Skip to content
Snippets Groups Projects
Select Git revision
  • dailybuild_linux-x64
  • dailybuild_win32
  • master default protected
  • sqlite
  • rip_abstraction
  • dailybuild_macos-armv8
  • dd_file_lister_filanem_in_desc_color
  • mode7
  • dd_msg_reader_are_you_there_warning_improvement
  • c23-playing
  • syncterm-1.3
  • syncterm-1.2
  • test-build
  • hide_remote_connection_with_telgate
  • 638-can-t-control-c-during-a-file-search
  • add_body_to_pager_email
  • mingw32-build
  • cryptlib-3.4.7
  • ree/mastermind
  • new_user_dat
  • sbbs320d
  • syncterm-1.6
  • syncterm-1.5
  • syncterm-1.4
  • sbbs320b
  • syncterm-1.3
  • syncterm-1.2
  • syncterm-1.2rc6
  • syncterm-1.2rc5
  • push
  • syncterm-1.2rc4
  • syncterm-1.2rc2
  • syncterm-1.2rc1
  • sbbs319b
  • sbbs318b
  • goodbuild_linux-x64_Sep-01-2020
  • goodbuild_win32_Sep-01-2020
  • goodbuild_linux-x64_Aug-31-2020
  • goodbuild_win32_Aug-31-2020
  • goodbuild_win32_Aug-30-2020
40 results

js_console.cpp

Blame
  • js_console.cpp 64.10 KiB
    /* js_console.cpp */
    
    /* Synchronet JavaScript "Console" Object */
    
    /* $Id: js_console.cpp,v 1.154 2020/05/24 08:16:52 rswindell Exp $ */
    
    /****************************************************************************
     * @format.tab-size 4		(Plain Text/Source Code File Header)			*
     * @format.use-tabs true	(see http://www.synchro.net/ptsc_hdr.html)		*
     *																			*
     * Copyright Rob Swindell - http://www.synchro.net/copyright.html			*
     *																			*
     * This program is free software; you can redistribute it and/or			*
     * modify it under the terms of the GNU General Public License				*
     * as published by the Free Software Foundation; either version 2			*
     * of the License, or (at your option) any later version.					*
     * See the GNU General Public License for more details: gpl.txt or			*
     * http://www.fsf.org/copyleft/gpl.html										*
     *																			*
     * Anonymous FTP access to the most recent released source is available at	*
     * ftp://vert.synchro.net, ftp://cvs.synchro.net and ftp://ftp.synchro.net	*
     *																			*
     * Anonymous CVS access to the development source and modification history	*
     * is available at cvs.synchro.net:/cvsroot/sbbs, example:					*
     * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs login			*
     *     (just hit return, no password is necessary)							*
     * cvs -d :pserver:anonymous@cvs.synchro.net:/cvsroot/sbbs checkout src		*
     *																			*
     * For Synchronet coding style and modification guidelines, see				*
     * http://www.synchro.net/source.html										*
     *																			*
     * You are encouraged to submit any modifications (preferably in Unix diff	*
     * format) via e-mail to mods@synchro.net									*
     *																			*
     * Note: If this box doesn't appear square, then you need to fix your tabs.	*
     ****************************************************************************/
    
    #include "sbbs.h"
    #include "js_request.h"
    
    #ifdef JAVASCRIPT
    
    /*****************************/
    /* Console Object Properites */
    /*****************************/
    enum {
    	 CON_PROP_STATUS
    	,CON_PROP_MOUSE_MODE
    	,CON_PROP_LNCNTR
    	,CON_PROP_COLUMN
    	,CON_PROP_LASTLINELEN
    	,CON_PROP_ATTR
    	,CON_PROP_TOS
    	,CON_PROP_ROW
    	,CON_PROP_ROWS
    	,CON_PROP_COLUMNS
    	,CON_PROP_TABSTOP
    	,CON_PROP_AUTOTERM
    	,CON_PROP_TERMINAL
    	,CON_PROP_TERM_TYPE
    	,CON_PROP_CHARSET
    	,CON_PROP_CTERM_VERSION
    	,CON_PROP_WORDWRAP
    	,CON_PROP_QUESTION
    	,CON_PROP_INACTIV_WARN
    	,CON_PROP_INACTIV_HANGUP
    	,CON_PROP_TIMEOUT			/* User inactivity timeout reference */
    	,CON_PROP_TIMELEFT_WARN		/* low timeleft warning counter */
    	,CON_PROP_ABORTED
    	,CON_PROP_ABORTABLE
    	,CON_PROP_TELNET_MODE
    	,CON_PROP_GETSTR_OFFSET
    	,CON_PROP_CTRLKEY_PASSTHRU
    	/* read only */
    	,CON_PROP_INBUF_LEVEL
    	,CON_PROP_INBUF_SPACE
    	,CON_PROP_OUTBUF_LEVEL
    	,CON_PROP_OUTBUF_SPACE
    	,CON_PROP_KEYBUF_LEVEL
    	,CON_PROP_KEYBUF_SPACE
    
    	,CON_PROP_OUTPUT_RATE
    };
    
    extern JSClass js_console_class;
    static JSBool js_console_get(JSContext *cx, JSObject *obj, jsid id, jsval *vp)
    {
    	jsval		idval;
    	int32		val;
        jsint       tiny;
    	JSString*	js_str;
    	sbbs_t*		sbbs;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, obj, &js_console_class))==NULL)
    		return(JS_FALSE);
    
        JS_IdToValue(cx, id, &idval);
        tiny = JSVAL_TO_INT(idval);
    
    	switch(tiny) {
    		case CON_PROP_STATUS:
    			val=sbbs->console;
    			break;
    		case CON_PROP_MOUSE_MODE:
    			val=sbbs->mouse_mode;
    			break;
    		case CON_PROP_LNCNTR:
    			val=sbbs->lncntr;
    			break;
    		case CON_PROP_COLUMN:
    			val=sbbs->column;
    			break;
    		case CON_PROP_LASTLINELEN:
    			val=sbbs->lastlinelen;
    			break;
    		case CON_PROP_ATTR:
    			val=sbbs->curatr;
    			break;
    		case CON_PROP_TOS:
    			val=sbbs->row == 0;
    			break;
    		case CON_PROP_ROW:
    			val=sbbs->row;
    			break;
    		case CON_PROP_ROWS:
    			val=sbbs->rows;
    			break;
    		case CON_PROP_COLUMNS:
    			val=sbbs->cols;
    			break;
    		case CON_PROP_TABSTOP:
    			val=sbbs->tabstop;
    			break;
    		case CON_PROP_AUTOTERM:
    			val=sbbs->autoterm;
    			break;
    		case CON_PROP_TERMINAL:
    			if((js_str=JS_NewStringCopyZ(cx, sbbs->terminal))==NULL)
    				return(JS_FALSE);
    			*vp = STRING_TO_JSVAL(js_str);
    			return(JS_TRUE);
    		case CON_PROP_TERM_TYPE:
    			if((js_str=JS_NewStringCopyZ(cx, sbbs->term_type()))==NULL)
    				return(JS_FALSE);
    			*vp = STRING_TO_JSVAL(js_str);
    			return(JS_TRUE);
    		case CON_PROP_CHARSET:
    			if((js_str=JS_NewStringCopyZ(cx, sbbs->term_charset()))==NULL)
    				return(JS_FALSE);
    			*vp = STRING_TO_JSVAL(js_str);
    			return(JS_TRUE);
    		case CON_PROP_CTERM_VERSION:
    			val=sbbs->cterm_version;
    			break;
    
    		case CON_PROP_INACTIV_WARN:
    			val=sbbs->cfg.sec_warn;
    			break;
    		case CON_PROP_INACTIV_HANGUP:
    			val=sbbs->cfg.sec_hangup;
    			break;
    		case CON_PROP_TIMEOUT:
    			val=(int32)sbbs->timeout;
    			break;
    		case CON_PROP_TIMELEFT_WARN:
    			val=sbbs->timeleft_warn;
    			break;
    		case CON_PROP_ABORTED:
    			*vp=BOOLEAN_TO_JSVAL(INT_TO_BOOL(sbbs->sys_status&SS_ABORT));
    			return(JS_TRUE);
    		case CON_PROP_ABORTABLE:
    			*vp=BOOLEAN_TO_JSVAL(INT_TO_BOOL(sbbs->rio_abortable));
    			return(JS_TRUE);
    		case CON_PROP_TELNET_MODE:
    			val=sbbs->telnet_mode;
    			break;
    		case CON_PROP_GETSTR_OFFSET:
    			val=sbbs->getstr_offset;
    			break;
    		case CON_PROP_WORDWRAP:
    			if((js_str=JS_NewStringCopyZ(cx, sbbs->wordwrap))==NULL)
    				return(JS_FALSE);
    			*vp = STRING_TO_JSVAL(js_str);
    			return(JS_TRUE);
    		case CON_PROP_QUESTION:
    			if((js_str=JS_NewStringCopyZ(cx, sbbs->question))==NULL)
    				return(JS_FALSE);
    			*vp = STRING_TO_JSVAL(js_str);
    			return(JS_TRUE);
    		case CON_PROP_CTRLKEY_PASSTHRU:
    			val=sbbs->cfg.ctrlkey_passthru;
    			break;
    		case CON_PROP_INBUF_LEVEL:
    			val=RingBufFull(&sbbs->inbuf);
    			break;
    		case CON_PROP_INBUF_SPACE:
    			val=RingBufFree(&sbbs->inbuf);
    			break;
    		case CON_PROP_OUTBUF_LEVEL:
    			val=RingBufFull(&sbbs->outbuf);
    			break;
    		case CON_PROP_OUTBUF_SPACE:
    			val=RingBufFree(&sbbs->outbuf);
    			break;
    		case CON_PROP_OUTPUT_RATE:
    			val = sbbs->cur_output_rate;
    			break;
    		case CON_PROP_KEYBUF_LEVEL:
    			val = sbbs->keybuf_level();
    			break;
    		case CON_PROP_KEYBUF_SPACE:
    			val = sbbs->keybuf_space();
    			break;
    
    		default:
    			return(JS_TRUE);
    	}
    
    	*vp = INT_TO_JSVAL(val);
    
    	return(JS_TRUE);
    }
    
    static JSBool js_console_set(JSContext *cx, JSObject *obj, jsid id, JSBool strict, jsval *vp)
    {
    	jsval idval;
    	int32		val=0;
        jsint       tiny;
    	sbbs_t*		sbbs;
    	JSString*	str;
    	jsrefcount	rc;
    	char		*sval;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, obj, &js_console_class))==NULL)
    		return(JS_FALSE);
    
        JS_IdToValue(cx, id, &idval);
        tiny = JSVAL_TO_INT(idval);
    
    	if(JSVAL_IS_NUMBER(*vp) || JSVAL_IS_BOOLEAN(*vp)) {
    		if(!JS_ValueToInt32(cx, *vp, &val))
    			return JS_FALSE;
    	}
    
    	switch(tiny) {
    		case CON_PROP_STATUS:
    			sbbs->console=val;
    			break;
    		case CON_PROP_MOUSE_MODE:
    			sbbs->set_mouse(val);
    			break;
    		case CON_PROP_LNCNTR:
    			sbbs->lncntr=val;
    			break;
    		case CON_PROP_COLUMN:
    			sbbs->column=val;
    			break;
    		case CON_PROP_LASTLINELEN:
    			sbbs->lastlinelen=val;
    			break;
    		case CON_PROP_ATTR:
    			if(JSVAL_IS_STRING(*vp)) {
    				JSVALUE_TO_MSTRING(cx, *vp, sval, NULL);
    				if(sval==NULL)
    					break;
    				val=attrstr(sval);
    				free(sval);
    			}
    			rc=JS_SUSPENDREQUEST(cx);
    			sbbs->attr(val);
    			JS_RESUMEREQUEST(cx, rc);
    			break;
    		case CON_PROP_ROW:
    			if(val >= 0 && val < TERM_ROWS_MAX)
    				sbbs->row = val;
    			break;
    		case CON_PROP_ROWS:
    			if(val >= TERM_ROWS_MIN && val <= TERM_ROWS_MAX)
    				sbbs->rows=val;
    			break;
    		case CON_PROP_COLUMNS:
    			if(val >= TERM_COLS_MIN && val <= TERM_COLS_MAX)
    				sbbs->cols=val;
    			break;
    		case CON_PROP_TABSTOP:
    			sbbs->tabstop=val;
    			break;
    		case CON_PROP_AUTOTERM:
    			sbbs->autoterm=val;
    			break;
    		case CON_PROP_TERMINAL:
    			JSVALUE_TO_MSTRING(cx, *vp, sval, NULL);
    			if(sval==NULL)
    				break;
    			SAFECOPY(sbbs->terminal,sval);
    			free(sval);
    			break;
    		case CON_PROP_CTERM_VERSION:
    			sbbs->cterm_version = val;
    			break;
    		case CON_PROP_INACTIV_WARN:
    			sbbs->cfg.sec_warn = (uint16_t)val;
    			break;
    		case CON_PROP_INACTIV_HANGUP:
    			sbbs->cfg.sec_hangup = (uint16_t)val;
    			break;
    		case CON_PROP_TIMEOUT:
    			sbbs->timeout=val;
    			break;
    		case CON_PROP_TIMELEFT_WARN:
    			sbbs->timeleft_warn=val;
    			break;
    		case CON_PROP_ABORTED:
    			if(val)
    				sbbs->sys_status|=SS_ABORT;
    			else
    				sbbs->sys_status&=~SS_ABORT;
    			break;
    		case CON_PROP_ABORTABLE:
    			sbbs->rio_abortable=val
    				? true:false; // This is a dumb bool conversion to make BC++ happy
    			break;
    		case CON_PROP_TELNET_MODE:
    			sbbs->telnet_mode=val;
    			break;
    		case CON_PROP_GETSTR_OFFSET:
    			sbbs->getstr_offset=val;
    			break;
    		case CON_PROP_QUESTION:
    			JSVALUE_TO_MSTRING(cx, *vp, sval, NULL);
    			if(sval==NULL)
    				break;
    			SAFECOPY(sbbs->question,sval);
    			free(sval);
    			break;
    		case CON_PROP_CTRLKEY_PASSTHRU:
    			if(JSVAL_IS_STRING(*vp)) {
    				char *s;
    
    				if((str=JS_ValueToString(cx, *vp))==NULL)
    					break;
    				JSSTRING_TO_MSTRING(cx, str, s, NULL);
    				if(s==NULL)
    					break;
    				val=str_to_bits(sbbs->cfg.ctrlkey_passthru, s);
    				free(s);
    			}
    			sbbs->cfg.ctrlkey_passthru=val;
    			break;
    		case CON_PROP_OUTPUT_RATE:
    			sbbs->set_output_rate((enum sbbs_t::output_rate)val);
    			break;
    
    		default:
    			return(JS_TRUE);
    	}
    
    	return(JS_TRUE);
    }
    
    #define CON_PROP_FLAGS JSPROP_ENUMERATE
    
    static jsSyncPropertySpec js_console_properties[] = {
    /*		 name				,tinyid						,flags			,ver	*/
    
    	{	"status"			,CON_PROP_STATUS			,CON_PROP_FLAGS	,310},
    	{	"mouse_mode"		,CON_PROP_MOUSE_MODE		,CON_PROP_FLAGS, 31800},
    	{	"line_counter"		,CON_PROP_LNCNTR 			,CON_PROP_FLAGS	,310},
    	{	"current_row"		,CON_PROP_ROW				,CON_PROP_FLAGS ,31800},
    	{	"current_column"	,CON_PROP_COLUMN			,CON_PROP_FLAGS ,315},
    	{	"last_line_length"	,CON_PROP_LASTLINELEN		,CON_PROP_FLAGS	,317},
    	{	"attributes"		,CON_PROP_ATTR				,CON_PROP_FLAGS	,310},
    	{	"top_of_screen"		,CON_PROP_TOS				,JSPROP_ENUMERATE|JSPROP_READONLY	,310},
    	{	"screen_rows"		,CON_PROP_ROWS				,CON_PROP_FLAGS	,310},
    	{	"screen_columns"	,CON_PROP_COLUMNS			,CON_PROP_FLAGS	,311},
    	{	"tabstop"			,CON_PROP_TABSTOP			,CON_PROP_FLAGS	,31700},
    	{	"autoterm"			,CON_PROP_AUTOTERM			,CON_PROP_FLAGS	,310},
    	{	"terminal"			,CON_PROP_TERMINAL			,CON_PROP_FLAGS ,311},
    	{	"type"				,CON_PROP_TERM_TYPE			,JSPROP_ENUMERATE|JSPROP_READONLY ,31702},
    	{	"charset"			,CON_PROP_CHARSET			,JSPROP_ENUMERATE|JSPROP_READONLY ,31702},
    	{	"cterm_version"		,CON_PROP_CTERM_VERSION		,CON_PROP_FLAGS ,317},
    	{	"inactivity_warning",CON_PROP_INACTIV_WARN		,CON_PROP_FLAGS, 31401},
    	{	"inactivity_hangup"	,CON_PROP_INACTIV_HANGUP	,CON_PROP_FLAGS, 31401},
    	{	"timeout"			,CON_PROP_TIMEOUT			,CON_PROP_FLAGS	,310},
    	{	"timeleft_warning"	,CON_PROP_TIMELEFT_WARN		,CON_PROP_FLAGS	,310},
    	{	"aborted"			,CON_PROP_ABORTED			,CON_PROP_FLAGS	,310},
    	{	"abortable"			,CON_PROP_ABORTABLE			,CON_PROP_FLAGS	,310},
    	{	"telnet_mode"		,CON_PROP_TELNET_MODE		,CON_PROP_FLAGS	,310},
    	{	"wordwrap"			,CON_PROP_WORDWRAP			,JSPROP_ENUMERATE|JSPROP_READONLY ,310},
    	{	"question"			,CON_PROP_QUESTION			,CON_PROP_FLAGS ,310},
    	{	"getstr_offset"		,CON_PROP_GETSTR_OFFSET		,CON_PROP_FLAGS ,311},
    	{	"ctrlkey_passthru"	,CON_PROP_CTRLKEY_PASSTHRU	,CON_PROP_FLAGS	,310},
    	{	"input_buffer_level",CON_PROP_INBUF_LEVEL		,JSPROP_ENUMERATE|JSPROP_READONLY, 312},
    	{	"input_buffer_space",CON_PROP_INBUF_SPACE		,JSPROP_ENUMERATE|JSPROP_READONLY, 312},
    	{	"output_buffer_level",CON_PROP_OUTBUF_LEVEL		,JSPROP_ENUMERATE|JSPROP_READONLY, 312},
    	{	"output_buffer_space",CON_PROP_OUTBUF_SPACE		,JSPROP_ENUMERATE|JSPROP_READONLY, 312},
    	{	"output_rate"		,CON_PROP_OUTPUT_RATE		,JSPROP_ENUMERATE, 31702},
    	{	"keyboard_buffer_level",CON_PROP_KEYBUF_LEVEL	,JSPROP_ENUMERATE|JSPROP_READONLY, 31800},
    	{	"keyboard_buffer_space",CON_PROP_KEYBUF_SPACE	,JSPROP_ENUMERATE|JSPROP_READONLY, 31800},
    	{0}
    };
    
    #ifdef BUILD_JSDOCS
    static const char* con_prop_desc[] = {
    	 "status bit-field (see <tt>CON_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions)"
    	,"mouse mode bit-field (see <tt>MOUSE_MODE_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions)"
    	,"current 0-based line counter (used for automatic screen pause)"
    	,"current 0-based row counter"
    	,"current 0-based column counter (used to auto-increment <i>line_counter</i> when screen wraps)"
    	,"length of last line sent to terminal (before a carriage-return or line-wrap)"
    	,"current display attributes (set with number or string value)"
    	,"set to <i>true</i> if the terminal cursor is already at the top of the screen - <small>READ ONLY</small>"
    	,"number of remote terminal screen rows (in lines)"
    	,"number of remote terminal screen columns (in character cells)"
    	,"current tab stop interval (tab size), in columns"
    	,"bit-field of automatically detected terminal settings "
    		"(see <tt>USER_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions)"
    	,"terminal type description, possibly supplied by client (e.g. 'ANSI')"
    	,"terminal type (i.e. 'ANSI', 'RIP', 'PETSCII', or 'DUMB')"
    	,"terminal character set (i.e. 'UTF-8', 'CP437', 'CBM-ASCII', or 'US-ASCII')"
    	,"detected CTerm (SyncTERM) version as an integer > 1000 where major version is cterm_version / 1000 and minor version is cterm_version % 1000"
    	,"number of seconds before displaying warning (Are you really there?) due to user/keyboard inactivity"
    	,"number of seconds before disconnection due to user/keyboard inactivity"
    	,"user/keyboard inactivity timeout reference value (time_t format)"
    	,"number of low time-left (5 or fewer minutes remaining) warnings displayed to user"
    	,"input/output has been aborted"
    	,"remote output can be asynchronously aborted with Ctrl-C"
    	,"current Telnet mode bit-field (see <tt>TELNET_MODE_*</tt> in <tt>sbbsdefs.js</tt> for bit definitions)"
    	,"word-wrap buffer (used by getstr) - <small>READ ONLY</small>"
    	,"current yes/no question (set by yesno and noyes)"
    	,"cursor position offset for use with <tt>getstr(K_USEOFFSET)</tt>"
    	,"control key pass-through bit-mask, set bits represent control key combinations "
    		"<i>not</i> handled by <tt>inkey()</tt> method "
    		"This may optionally be specified as a string of characters. "
    		"The format of this string is [+-][@-_]. If neither plus nor minus is "
    		"the first character, the value will be replaced by one constructed "
    		"from the string. A + indicates that characters following will be "
    		"added to the set, and a - indicates they should be removed. "
    		"ex: <tt>console.ctrlkey_passthru=\"-UP+AB\"</tt> will clear CTRL-U and "
    		"CTRL-P and set CTRL-A and CTRL-B."
    	,"number of bytes currently in the input buffer (from the remote client) - <small>READ ONLY</small>"
    	,"number of bytes available in the input buffer	- <small>READ ONLY</small>"
    	,"number of bytes currently in the output buffer (from the local server) - <small>READ ONLY</small>"
    	,"number of bytes available in the output buffer - <small>READ ONLY</small>"
    	,"emulated serial data output rate, in bits-per-second (0 = unlimited)"
    	,"number of characters current in the keyboard input buffer (from <tt>ungetstr</tt>) - <small>READ ONLY</small>"
    	,"number of characters spaces available in the keyboard input buffer - <small>READ ONLY</small>"
    	,NULL
    };
    #endif
    
    /**************************/
    /* Console Object Methods */
    /**************************/
    
    static JSBool
    js_inkey(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char		key[2];
    	int32		mode=0;
    	int32		timeout=0;
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&mode))
    			return JS_FALSE;
    	}
    	if(argc>1) {
    		if(!JS_ValueToInt32(cx,argv[1],&timeout))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	key[0]=sbbs->inkey(mode,timeout);
    	JS_RESUMEREQUEST(cx, rc);
    	key[1]=0;
    
    	if((js_str = JS_NewStringCopyZ(cx, key))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
        return(JS_TRUE);
    }
    
    static JSBool
    js_getkey(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char		key[2];
    	int32		mode=0;
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&mode))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	key[0]=sbbs->getkey(mode);
    	JS_RESUMEREQUEST(cx, rc);
    	key[1]=0;
    
    	if((js_str = JS_NewStringCopyZ(cx, key))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
        return(JS_TRUE);
    }
    
    static JSBool
    js_getbyte(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		timeout=0;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return JS_FALSE;
    
    	JS_SET_RVAL(cx, arglist, JSVAL_NULL);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0], &timeout))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	int32 byte = sbbs->incom(timeout);
    	JS_RESUMEREQUEST(cx, rc);
    
    	if(byte != NOINP)
    		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(byte));
        return JS_TRUE;
    }
    
    static JSBool
    js_putbyte(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		byte=0;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return JS_FALSE;
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0], &byte))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	int result = sbbs->outcom(byte);
    	JS_RESUMEREQUEST(cx, rc);
    
    	JS_SET_RVAL(cx, arglist, result == 0 ? JSVAL_TRUE : JSVAL_FALSE);
        return JS_TRUE ;
    }
    
    static JSBool
    js_add_hotspot(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return JS_FALSE;
    
    	if(argc < 1) {
    		JS_ReportError(cx, "Invalid number of arguments to function");
    		return JS_FALSE;
    	}
    
    	JSString*	js_str = JS_ValueToString(cx, argv[0]);
    	if(js_str == NULL)
    		return JS_FALSE;
    	bool hungry = true;
    	int32 min_x = -1;
    	int32 max_x = -1;
    	int32 y = -1;
    	uintN argn = 1;
    	if(argc > argn && JSVAL_IS_BOOLEAN(argv[argn])) {
    		hungry = JSVAL_TO_BOOLEAN(argv[argn]);
    		argn++;
    	}
    	if(argc > argn) {
    		if(!JS_ValueToInt32(cx,argv[argn], &min_x))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc > argn) {
    		if(!JS_ValueToInt32(cx,argv[argn], &max_x))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc > argn) {
    		if(!JS_ValueToInt32(cx,argv[argn], &y))
    			return JS_FALSE;
    		argn++;
    	}
    	char* p = NULL;
    	JSSTRING_TO_MSTRING(cx, js_str, p, NULL);
    	if(p == NULL)
    		return JS_FALSE;
    	sbbs->add_hotspot(p, hungry, min_x, max_x, y);
    	free(p);
        return JS_TRUE;
    }
    
    static JSBool js_scroll_hotspots(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return JS_FALSE;
    
    	int32 rows = 1;
    	if(argc > 0 && !JS_ValueToInt32(cx,argv[0], &rows))
    		return JS_FALSE;
    	sbbs->scroll_hotspots(rows);
        return JS_TRUE;
    }
    
    static JSBool js_clear_hotspots(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return JS_FALSE;
    
    	sbbs->clear_hotspots();
        return JS_TRUE;
    }
    
    static JSBool
    js_handle_ctrlkey(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char		key;
    	int32		mode=0;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    	char		*keystr;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
    
    	if(JSVAL_IS_INT(argv[0]))
    		key=(char)JSVAL_TO_INT(argv[0]);
    	else {
    		JSVALUE_TO_ASTRING(cx, argv[0], keystr, 2, NULL);
    		if(keystr==NULL)
    			return(JS_FALSE);
    		key=keystr[0];
    	}
    
    	if(argc>1) {
    		if(!JS_ValueToInt32(cx, argv[1], &mode))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->handle_ctrlkey(key,mode)==0));
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_getstr(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char		*p,*p2;
    	int32		mode=0;
    	uintN		i;
    	int32		maxlen=0;
    	sbbs_t*		sbbs;
        JSString*	js_str=NULL;
    	jsrefcount	rc;
    	str_list_t	history = NULL;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	for(i=0;i<argc;i++) {
    		if(JSVAL_IS_NUMBER(argv[i])) {
    			if(!maxlen) {
    				if(!JS_ValueToInt32(cx,argv[i],&maxlen))
    					return JS_FALSE;
    			}
    			else {
    				if(!JS_ValueToInt32(cx,argv[i],&mode))
    					return JS_FALSE;
    			}
    		}
    		else if(JSVAL_IS_STRING(argv[i])) {
    			js_str = JS_ValueToString(cx, argv[i]);
    			if (!js_str)
    			    return(JS_FALSE);
    		}
    		else if(JSVAL_IS_OBJECT(argv[i])) {
    			JSObject* array = JSVAL_TO_OBJECT(argv[i]);
    			jsuint len=0;
    			if(!JS_GetArrayLength(cx, array, &len))
    				return JS_FALSE;
    			history = (str_list_t)alloca(sizeof(char*) * (len + 1));
    			memset(history, 0, sizeof(char*) * (len + 1));
    			for(jsuint j=0; j < len; j++) {
    				jsval		val;
    				if(!JS_GetElement(cx, array, j, &val))
    					break;
    				JSString* hist = JS_ValueToString(cx, val);
    				if (hist == NULL)
    					return JS_FALSE;
    				char* cstr = NULL;
    				JSSTRING_TO_ASTRING(cx, hist, cstr, (uint)(maxlen ? maxlen : 80), NULL);
    				if(cstr == NULL)
    					return JS_FALSE;
    				history[j] = cstr;
    			}
    		}
    	}
    
    	if(!maxlen) maxlen=128;
    
    	if((p=(char *)calloc(1,maxlen+1))==NULL)
    		return(JS_FALSE);
    
    	if(js_str!=NULL) {
    		JSSTRING_TO_MSTRING(cx, js_str, p2, NULL);
    		if(p2==NULL) {
    			free(p);
    			return JS_FALSE;
    		}
    		sprintf(p,"%.*s",(int)maxlen,p2);
    		free(p2);
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->getstr(p, maxlen, mode, history);
    	JS_RESUMEREQUEST(cx, rc);
    
    	js_str = JS_NewStringCopyZ(cx, p);
    
    	free(p);
    
    	if(js_str==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
        return(JS_TRUE);
    }
    
    static JSBool
    js_getnum(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	uint32_t	maxnum=~0;
    	int32		dflt=0;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc && JSVAL_IS_NUMBER(argv[0])) {
    		if(!JS_ValueToInt32(cx,argv[0],(int32*)&maxnum))
    			return JS_FALSE;
    	}
    	if(argc>1 && JSVAL_IS_NUMBER(argv[1])) {
    		if(!JS_ValueToInt32(cx,argv[1],&dflt))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->getnum(maxnum,dflt)));
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_getkeys(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char		key[2];
    	uintN		i;
    	int32		val;
    	uint32		maxnum = ~0;
    	bool		maxnum_specified = false;
    	long		mode = K_UPPER;
    	sbbs_t*		sbbs;
        JSString*	js_str=NULL;
    	char*		cstr=NULL;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	for(i=0;i<argc;i++) {
    		if(JSVAL_IS_NUMBER(argv[i])) {
    			if(!JS_ValueToInt32(cx, argv[i], &val))
    				return JS_FALSE;
    			if(!maxnum_specified) {
    				maxnum_specified = true;
    				maxnum = val;
    			} else
    				mode = val;
    			continue;
    		}
    		if(JSVAL_IS_STRING(argv[i])) {
    			js_str = JS_ValueToString(cx, argv[i]);
    			JSSTRING_TO_MSTRING(cx, js_str, cstr, NULL);
    			if(cstr==NULL)
    				return JS_FALSE;
    		}
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	val=sbbs->getkeys(cstr, maxnum, mode);
    	FREE_AND_NULL(cstr);
    	JS_RESUMEREQUEST(cx, rc);
    
    	if(val==-1) {			// abort
    		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(0));
    	} else if(val<0) {		// number
    		val&=~0x80000000;
    		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(val));
    	} else {				// key
    		key[0]=(uchar)val;
    		key[1]=0;
    		if((js_str = JS_NewStringCopyZ(cx, key))==NULL)
    			return(JS_FALSE);
    		JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
    	}
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_gettemplate(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char		str[128];
    	int32		mode=0;
    	uintN		i;
    	sbbs_t*		sbbs;
        JSString*	js_str=NULL;
        JSString*	js_fmt=NULL;
    	jsrefcount	rc;
    	char*		cstr;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	for(i=0;i<argc;i++) {
    		if(JSVAL_IS_STRING(argv[i])) {
    			if(js_fmt==NULL)
    				js_fmt = JS_ValueToString(cx, argv[i]);
    			else
    				js_str = JS_ValueToString(cx, argv[i]);
    		} else if(JSVAL_IS_NUMBER(argv[i])) {
    			if(!JS_ValueToInt32(cx,argv[i],(int32*)&mode))
    				return JS_FALSE;
    		}
    	}
    
    	if(js_fmt==NULL)
    		return(JS_FALSE);
    
    	if(js_str==NULL)
    		str[0]=0;
    	else {
    		JSSTRING_TO_STRBUF(cx, js_str, str, sizeof(str), NULL);
    	}
    
    	JSSTRING_TO_MSTRING(cx, js_fmt, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->gettmplt(str,cstr,mode);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
    
    	if((js_str=JS_NewStringCopyZ(cx, str))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
        return(JS_TRUE);
    }
    
    static JSBool
    js_ungetstr(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	char*		p;
    	char*		op;
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
    		return(JS_FALSE);
    
    	JSSTRING_TO_MSTRING(cx, js_str, op, NULL);
    	if(op==NULL)
    		return JS_FALSE;
    
    	p=op;
    	rc=JS_SUSPENDREQUEST(cx);
    	while(p && *p)
    		sbbs->ungetkey(*(p++));
    	free(op);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_yesno(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	char*		cstr;
    	int32		mode = P_NONE;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
    		return(JS_FALSE);
    
    	JSSTRING_TO_MSTRING(cx, js_str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	if(argc > 1 && JSVAL_IS_NUMBER(argv[1])) {
    		if(!JS_ValueToInt32(cx, argv[1], &mode))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->yesno(cstr, mode)));
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_noyes(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	char*		cstr;
    	int32		mode = P_NONE;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
    		return(JS_FALSE);
    
    	JSSTRING_TO_MSTRING(cx, js_str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	if(argc > 1 && JSVAL_IS_NUMBER(argv[1])) {
    		if(!JS_ValueToInt32(cx, argv[1], &mode))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->noyes(cstr, mode)));
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_mnemonics(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	char*		cstr;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((js_str=JS_ValueToString(cx, argv[0]))==NULL)
    		return(JS_FALSE);
    
    	JSSTRING_TO_MSTRING(cx, js_str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->mnemonics(cstr);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_set_attr(JSContext* cx, sbbs_t* sbbs, jsval val)
    {
    	int32		attr;
    	char		*as;
    	jsrefcount	rc;
    
    	if(JSVAL_IS_STRING(val)) {
    		JSVALUE_TO_MSTRING(cx, val, as, NULL);
    		if(as==NULL)
    			return JS_FALSE;
    		attr=attrstr(as);
    		free(as);
    	}
    	else {
    		if(!JS_ValueToInt32(cx, val, &attr))
    			return JS_FALSE;;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->attr(attr);
    	JS_RESUMEREQUEST(cx, rc);
    	return JS_TRUE;
    }
    
    static JSBool
    js_clear(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	bool		autopause = true;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	uintN argn = 0;
    	if(argc > argn && !JSVAL_IS_BOOLEAN(argv[argn])) {
    		if(!js_set_attr(cx, sbbs, argv[argn]))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc > argn && JSVAL_IS_BOOLEAN(argv[argn])) {
    		autopause = JSVAL_TO_BOOLEAN(argv[argn]);
    		argn++;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	if(autopause)
    		sbbs->CLS;
    	else
    		sbbs->clearscreen(sbbs->term_supports());
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_clearline(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!js_set_attr(cx, sbbs, argv[0]))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->clearline();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_cleartoeol(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!js_set_attr(cx, sbbs, argv[0]))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cleartoeol();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_crlf(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    	int32		i;
    	int32		count=1;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx, argv[0], &count))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	for(i=0;i<count;i++) {
    		sbbs->outchar(CR);
    		sbbs->outchar(LF);
    	}
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_pause(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->pause();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_beep(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	int32		i;
    	int32		count=1;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx, argv[0], &count))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	for(i=0;i<count;i++)
    		sbbs->outchar('\a');
    	JS_RESUMEREQUEST(cx, rc);
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_print(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	uintN		i;
    	sbbs_t*		sbbs;
    	char*		cstr=NULL;
    	size_t		cstr_sz=0;
    	jsrefcount	rc;
    	int32		pmode = 0;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc == 2 && JSVAL_IS_STRING(argv[0]) && JSVAL_IS_NUMBER(argv[1])) {
    		JSVALUE_TO_RASTRING(cx, argv[0], cstr, &cstr_sz, NULL);
    		if(cstr==NULL)
    		    return(JS_FALSE);
    		if(!JS_ValueToInt32(cx, argv[1], &pmode))
    			return JS_FALSE;
    		rc=JS_SUSPENDREQUEST(cx);
    		sbbs->bputs(cstr, pmode);
    		JS_RESUMEREQUEST(cx, rc);
    	}
        else for (i=0; i < argc; i++) {
    		JSVALUE_TO_RASTRING(cx, argv[i], cstr, &cstr_sz, NULL);
    
    		if(cstr==NULL)
    		    return(JS_FALSE);
    		rc=JS_SUSPENDREQUEST(cx);
    		sbbs->bputs(cstr);
    		JS_RESUMEREQUEST(cx, rc);
    	}
    	if(cstr)
    		free(cstr);
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_strlen(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
        JSString*	str;
    	char*		cstr;
    	jsrefcount	rc;
    	int32		pmode = 0;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((str=JS_ValueToString(cx, argv[0]))==NULL)
    		return(JS_FALSE);
    
    	if(argc > 1)
    		JS_ValueToInt32(cx, argv[1], &pmode);
    
    	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->bstrlen(cstr, pmode)));
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_write(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	uintN		i;
    	char*		str=NULL;
    	size_t		str_sz=0;
    	size_t		len;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
        for (i = 0; i < argc; i++) {
    		JSVALUE_TO_RASTRING(cx, argv[i], str, &str_sz, &len);
    		if(str==NULL)
    		    return JS_FALSE;
    		rc=JS_SUSPENDREQUEST(cx);
    		sbbs->rputs(str, len);
    		JS_RESUMEREQUEST(cx, rc);
    	}
    	free(str);
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_writeln(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!js_write(cx, argc, arglist))
    			return(JS_FALSE);
    	}
    	sbbs->rputs("\r\n");
    	return(JS_TRUE);
    }
    
    static JSBool
    js_putmsg(JSContext *cx, uintN argc, jsval *arglist)
    {
    	uintN		argn;
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		mode=0;
    	int32		columns=0;
        JSString*	str;
    	sbbs_t*		sbbs;
    	char*		cstr;
    	jsrefcount	rc;
    	JSObject*	obj = JS_GetScopeChain(cx);
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	str = JS_ValueToString(cx, argv[0]);
    	if (!str)
    		return(JS_FALSE);
    
    	argn = 1;
    	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
    		if(!JS_ValueToInt32(cx,argv[argn],&mode))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
    		if(!JS_ValueToInt32(cx,argv[argn],&columns))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc>argn && JSVAL_IS_OBJECT(argv[argn])) {
    		if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL)
    			return JS_FALSE;
    		argn++;
    	}
    
    	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->putmsg(cstr, mode, columns, obj);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_printfile(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		mode=0;
    	int32		columns=0;
        JSString*	str;
    	sbbs_t*		sbbs;
    	char*		cstr;
    	jsrefcount	rc;
    	JSObject*	obj = JS_GetScopeChain(cx);
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	if(JSVAL_NULL_OR_VOID(argv[0])) {
    		JS_ReportError(cx, "No filename specified");
    		return JS_FALSE;
    	}
    
    	str = JS_ValueToString(cx, argv[0]);
    	if (!str)
    		return(JS_FALSE);
    
    	uintN argn = 1;
    	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
    		if(!JS_ValueToInt32(cx,argv[argn],&mode))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc>argn && JSVAL_IS_NUMBER(argv[argn])) {
    		if(!JS_ValueToInt32(cx,argv[argn],&columns))
    			return JS_FALSE;
    		argn++;
    	}
    	if(argc>argn && JSVAL_IS_OBJECT(argv[argn])) {
    		if((obj = JSVAL_TO_OBJECT(argv[argn])) == NULL)
    			return JS_FALSE;
    		argn++;
    	}
    
    	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	bool result = sbbs->printfile(cstr,mode,columns, obj);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
    
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(result));
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_printtail(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		lines=0;
    	int32		mode=0;
    	int32		columns=0;
    	uintN		i;
    	sbbs_t*		sbbs;
        JSString*	js_str=NULL;
    	char*		cstr;
    	jsrefcount	rc;
    	JSObject*	obj = JS_GetScopeChain(cx);
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	for(i=0;i<argc;i++) {
    		if(JSVAL_IS_NUMBER(argv[i])) {
    			if(!lines) {
    				if(!JS_ValueToInt32(cx,argv[i],&lines))
    					return JS_FALSE;
    			}
    			else if(!mode){
    				if(!JS_ValueToInt32(cx,argv[i],&mode))
    					return JS_FALSE;
    			}
    			else {
    				if(!JS_ValueToInt32(cx,argv[i],&columns))
    					return JS_FALSE;
    			}
    		} else if(JSVAL_IS_STRING(argv[i])) {
    			js_str = JS_ValueToString(cx, argv[i]);
    		} else if(JSVAL_IS_OBJECT(argv[i])) {
    			if((obj = JSVAL_TO_OBJECT(argv[i])) == NULL)
    				return JS_FALSE;
    		}
    	}
    
    	if(js_str==NULL) {
    		JS_ReportError(cx, "No filename specified");
    		return JS_FALSE;
    	}
    
    	if(!lines)
    		lines=5;
    
    	JSSTRING_TO_MSTRING(cx, js_str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	bool result = sbbs->printtail(cstr,lines,mode,columns,obj);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
    
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(result));
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_editfile(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
        JSString*	str;
    	sbbs_t*		sbbs;
    	char*		cstr;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if((str=JS_ValueToString(cx, argv[0]))==NULL)
    		return(JS_FALSE);
    
    	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->editfile(cstr)));
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    
    static JSBool
    js_uselect(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	uintN		i;
    	int32		num=0;
    	char*		title=NULL;
    	char*		item=NULL;
    	uchar*		ar=NULL;
    	sbbs_t*		sbbs;
        JSString*	js_str;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(!argc) {
    		JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->uselect(0,0,NULL,NULL,NULL)));
    		return(JS_TRUE);
    	}
    
    	for(i=0;i<argc;i++) {
    		if(JSVAL_IS_NUMBER(argv[i])) {
    			if(!JS_ValueToInt32(cx,argv[i],&num)) {
    				FREE_AND_NULL(title);
    				FREE_AND_NULL(item);
    				return JS_FALSE;
    			}
    			continue;
    		}
    		if((js_str=JS_ValueToString(cx, argv[i]))==NULL) {
    			FREE_AND_NULL(title);
    			FREE_AND_NULL(item);
    			return(JS_FALSE);
    		}
    		if(title==NULL) {
    			JSSTRING_TO_MSTRING(cx, js_str, title, NULL)	// Magicsemicolon
    			if(title==NULL) {
    				FREE_AND_NULL(item);
    				return JS_FALSE;
    			}
    		}
    		else if(item==NULL) {
    			JSSTRING_TO_MSTRING(cx, js_str, item, NULL)	// Magicsemicolon
    			if(item==NULL) {
    				free(title);
    				return JS_FALSE;
    			}
    		}
    		else if(ar==NULL) {
    			char* ar_str=NULL;
    			JSSTRING_TO_MSTRING(cx, js_str, ar_str, NULL);
    			if(ar_str==NULL) {
    				free(item);
    				free(title);
    				return JS_FALSE;
    			}
    			ar=arstr(NULL,ar_str,&sbbs->cfg,NULL);
    			free(ar_str);
    		}
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, INT_TO_JSVAL(sbbs->uselect(1, num, title, item, ar)));
    	free(title);
    	free(item);
    	free(ar);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_center(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
        JSString*	str;
    	sbbs_t*		sbbs;
    	char*		cstr;
    	int32		cols = 0;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	str = JS_ValueToString(cx, argv[0]);
    	if (!str)
    		return(JS_FALSE);
    
    	if(argc > 1) {
    		if(!JS_ValueToInt32(cx, argv[1], &cols))
    			return JS_FALSE;
    	}
    
    	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->center(cstr, cols);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_wide(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
        JSString*	str;
    	sbbs_t*		sbbs;
    	char*		cstr;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	str = JS_ValueToString(cx, argv[0]);
    	if (str == NULL)
    		return(JS_FALSE);
    
    	JSSTRING_TO_MSTRING(cx, str, cstr, NULL);
    	if(cstr==NULL)
    		return JS_FALSE;
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->wide(cstr);
    	free(cstr);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_saveline(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->saveline()));
        return(JS_TRUE);
    }
    
    static JSBool
    js_restoreline(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->restoreline()));
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_ansi(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		attr=0;
    	JSString*	js_str;
    	sbbs_t*		sbbs;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&attr))
    			return JS_FALSE;
    	}
    	if(argc>1) {
    		int32	curattr=0;
    		char	buf[16];
    
    		if(!JS_ValueToInt32(cx,argv[1],&curattr))
    			return JS_FALSE;
    		if((js_str=JS_NewStringCopyZ(cx,sbbs->ansi(attr,curattr,buf)))==NULL)
    			return(JS_FALSE);
    	} else {
    		if((js_str=JS_NewStringCopyZ(cx,sbbs->ansi(attr)))==NULL)
    			return(JS_FALSE);
    	}
    
    	JS_SET_RVAL(cx, arglist, STRING_TO_JSVAL(js_str));
        return(JS_TRUE);
    }
    
    static JSBool
    js_pushxy(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->ansi_save();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_popxy(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->ansi_restore();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_gotoxy(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		x=1,y=1;
    	jsval		val;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(JSVAL_IS_OBJECT(argv[0])) {
    		JS_GetProperty(cx, JSVAL_TO_OBJECT(argv[0]),"x", &val);
    		if(!JS_ValueToInt32(cx,val,&x))
    			return JS_FALSE;
    		JS_GetProperty(cx, JSVAL_TO_OBJECT(argv[0]),"y", &val);
    		if(!JS_ValueToInt32(cx,val,&y))
    			return JS_FALSE;
    	} else {
    		if((!JS_ValueToInt32(cx,argv[0],&x)) ||
    				(!JS_ValueToInt32(cx,argv[1],&y)))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cursor_xy(x,y);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    
    static JSBool
    js_getxy(JSContext *cx, uintN argc, jsval *arglist)
    {
    	JSObject *obj=JS_THIS_OBJECT(cx, arglist);
    	sbbs_t*		sbbs;
    	int			x,y;
    	JSObject*	screen;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, obj, &js_console_class))==NULL)
     		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	bool result = sbbs->cursor_getxy(&x,&y);
    	JS_RESUMEREQUEST(cx, rc);
    
    	if(result == true) {
    		if((screen=JS_NewObject(cx,NULL,NULL,obj))==NULL)
    			return(JS_TRUE);
    
    		JS_DefineProperty(cx, screen, "x", INT_TO_JSVAL(x)
    			,NULL,NULL,JSPROP_ENUMERATE);
    		JS_DefineProperty(cx, screen, "y", INT_TO_JSVAL(y)
    			,NULL,NULL,JSPROP_ENUMERATE);
    
    		JS_SET_RVAL(cx, arglist, OBJECT_TO_JSVAL(screen));
    	}
        return(JS_TRUE);
    }
    
    static JSBool
    js_cursor_home(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cursor_home();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_cursor_up(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		val=1;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&val))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cursor_up(val);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_cursor_down(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		val=1;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&val))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cursor_down(val);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_cursor_right(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		val=1;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&val))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cursor_right(val);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_cursor_left(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	int32		val=1;
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&val))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->cursor_left(val);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_backspace(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    	int32		val=1;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    	if(argc) {
    		if(!JS_ValueToInt32(cx, argv[0], &val))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->backspace(val);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_creturn(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    	int32		val=1;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    	if(argc) {
    		if(!JS_ValueToInt32(cx, argv[0], &val))
    			return JS_FALSE;
    	}
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->carriage_return(val);
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    
    static JSBool
    js_clearkeybuf(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	sbbs->keybufbot=sbbs->keybuftop=0;
    	RingBufReInit(&sbbs->inbuf);
        return(JS_TRUE);
    }
    
    static JSBool
    js_getlines(JSContext *cx, uintN argc, jsval *arglist)
    {
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	sbbs->ansi_getlines();
    	JS_RESUMEREQUEST(cx, rc);
        return(JS_TRUE);
    }
    
    static JSBool
    js_lock_input(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	JSBool		lock=TRUE;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc)
    		JS_ValueToBoolean(cx, argv[0], &lock);
    
    	rc=JS_SUSPENDREQUEST(cx);
    	if(lock) {
    		pthread_mutex_lock(&sbbs->input_thread_mutex);
    	} else {
    		pthread_mutex_unlock(&sbbs->input_thread_mutex);
    	}
    	JS_RESUMEREQUEST(cx, rc);
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_telnet_cmd(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	int32		cmd,opt=0;
    	int32		wait=0;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(!JS_ValueToInt32(cx,argv[0],&cmd))
    		return JS_FALSE;
    	if(argc>1) {
    		if(!JS_ValueToInt32(cx,argv[1],&opt))
    			return JS_FALSE;
    	}
    	if(argc>2) {
    		if(!JS_ValueToInt32(cx,argv[2],&wait))
    			return JS_FALSE;
    	}
    
    	rc=JS_SUSPENDREQUEST(cx);
    	if(wait) {
    		if(sbbs->request_telnet_opt((uchar)cmd, (uchar)opt, wait) == true) {
    			JS_SET_RVAL(cx, arglist, JSVAL_TRUE);
    		} else {
    			JS_SET_RVAL(cx, arglist, JSVAL_FALSE);
    		}
    	} else
    		sbbs->send_telnet_cmd((uchar)cmd, (uchar)opt);
    	JS_RESUMEREQUEST(cx, rc);
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_term_supports(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	int32		flags;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return(JS_FALSE);
    
    	JS_SET_RVAL(cx, arglist, JSVAL_VOID);
    
    	if(argc) {
    		if(!JS_ValueToInt32(cx,argv[0],&flags))
    			return(JS_FALSE);
    		rc=JS_SUSPENDREQUEST(cx);
    		JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->term_supports(flags)));
    		JS_RESUMEREQUEST(cx, rc);
    	} else {
    		rc=JS_SUSPENDREQUEST(cx);
    		flags=sbbs->term_supports();
    		JS_RESUMEREQUEST(cx, rc);
    		JS_SET_RVAL(cx, arglist,INT_TO_JSVAL(flags));
    	}
    
        return(JS_TRUE);
    }
    
    static JSBool
    js_term_updated(JSContext *cx, uintN argc, jsval *arglist)
    {
    	jsval *argv=JS_ARGV(cx, arglist);
    	sbbs_t*		sbbs;
    	jsrefcount	rc;
    
    	if((sbbs=(sbbs_t*)js_GetClassPrivate(cx, JS_THIS_OBJECT(cx, arglist), &js_console_class))==NULL)
    		return JS_FALSE;
    
    	rc=JS_SUSPENDREQUEST(cx);
    	JS_SET_RVAL(cx, arglist, BOOLEAN_TO_JSVAL(sbbs->update_nodeterm()));
    	JS_RESUMEREQUEST(cx, rc);
    
        return JS_TRUE;
    }
    
    static jsSyncMethodSpec js_console_functions[] = {
    	{"inkey",			js_inkey,			0, JSTYPE_STRING,	JSDOCSTR("[mode=<tt>K_NONE</tt>] [,timeout=<tt>0</tt>]")
    	,JSDOCSTR("get a single key with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait).<br>"
    		"Returns an empty string if there is no input (e.g. timeout occurs).<br>"
    		"See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.")
    	,311
    	},
    	{"getkey",			js_getkey,			0, JSTYPE_STRING,	JSDOCSTR("[mode=<tt>K_NONE</tt>]")
    	,JSDOCSTR("get a single key, with wait.<br>"
    		"See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.")
    	,310
    	},
    	{"getstr",			js_getstr,			0, JSTYPE_STRING,	JSDOCSTR("[string] [,maxlen=<tt>128</tt>] [,mode=<tt>K_NONE</tt>] [,history[]]")
    	,JSDOCSTR("get a text string from the user.<br>"
    		"See <tt>K_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>"
    		"<i>history[]</i>, added in v3.17, allows a command history (string array) to be recalled using the up/down arrow keys."
    		)
    	,310
    	},
    	{"getnum",			js_getnum,			0, JSTYPE_NUMBER,	JSDOCSTR("[maxnum[, default]]")
    	,JSDOCSTR("get a number between 1 and <i>maxnum</i> from the user with a default value of <i>default</i>")
    	,310
    	},
    	{"getkeys",			js_getkeys,			1, JSTYPE_NUMBER,	JSDOCSTR("[keys] [,maxnum] [,mode=<tt>K_UPPER</tt>]")
    	,JSDOCSTR("get one key from of a list of valid command <i>keys</i> (any key, if not specified), "
    		"or a number between 1 and <i>maxnum</i>")
    	,310
    	},
    	{"gettemplate",		js_gettemplate,		1, JSTYPE_STRING,	JSDOCSTR("format [,string] [,mode=<tt>0</tt>]")
    	,JSDOCSTR("get an input string based on specified template")
    	,310
    	},
    	{"ungetstr",		js_ungetstr,		1, JSTYPE_VOID,		JSDOCSTR("keys")
    	,JSDOCSTR("put one or more characters in the keyboard input buffer")
    	,310
    	},
    	{"yesno",			js_yesno,			2, JSTYPE_BOOLEAN,	JSDOCSTR("question [,mode = P_NONE]")
    	,JSDOCSTR("YES/no question - returns <i>true</i> if 'yes' is selected")
    	,310
    	},
    	{"noyes",			js_noyes,			2, JSTYPE_BOOLEAN,	JSDOCSTR("question [,mode = P_NONE]")
    	,JSDOCSTR("NO/yes question - returns <i>true</i> if 'no' is selected")
    	,310
    	},
    	{"mnemonics",		js_mnemonics,		1, JSTYPE_VOID,		JSDOCSTR("text")
    	,JSDOCSTR("print a mnemonics string, command keys highlighted with tilde (~) characters")
    	,310
    	},
    	{"clear",           js_clear,			0, JSTYPE_VOID,		JSDOCSTR("[attribute] [,autopause=<tt>true</tt>]")
    	,JSDOCSTR("clear screen and home cursor, "
    		"optionally (in v3.13b+) setting current attribute first")
    	,310
    	},
    	{"home",            js_cursor_home,		0, JSTYPE_VOID,		JSDOCSTR("")
    	,JSDOCSTR("send cursor to home position (x,y:1,1)")
    	,311
    	},
    	{"clearline",       js_clearline,		0, JSTYPE_VOID,		JSDOCSTR("[attribute]")
    	,JSDOCSTR("clear current line, "
    		"optionally (in v3.13b+) setting current attribute first")
    	,310
    	},
    	{"cleartoeol",      js_cleartoeol,		0, JSTYPE_VOID,		JSDOCSTR("[attribute]")
    	,JSDOCSTR("clear to end-of-line, "
    		"optionally (in v3.13b+) setting current attribute first")
    	,311
    	},
    	{"crlf",            js_crlf,			0, JSTYPE_VOID,		JSDOCSTR("[count=<tt>1</tt>]")
    	,JSDOCSTR("output <i>count</i> number of carriage-return/line-feed pairs (new-lines)")
    	,310
    	},
    	{"pause",			js_pause,			0, JSTYPE_VOID,		JSDOCSTR("")
    	,JSDOCSTR("display pause prompt and wait for key hit")
    	,310
    	},
    	{"beep",			js_beep,			1, JSTYPE_VOID,		JSDOCSTR("[count=<tt>1</tt>]")
    	,JSDOCSTR("beep for <i>count</i> number of times (default count is 1)")
    	,311
    	},
    	{"print",			js_print,			1, JSTYPE_VOID,		JSDOCSTR("[value [,value][...]] or [string [,mode=<tt>P_NONE</tt>]]")
    	,JSDOCSTR("display one or more values as strings (supports Ctrl-A codes, Telnet-escaping, auto-screen pausing, etc.).<br>"
    		"Supports a limited set of <tt>P_*</tt> flags, e.g. <tt>P_PETSCII</tt> and <tt>P_UTF8</tt>."
    	)
    	,310
    	},
    	{"write",			js_write,			1, JSTYPE_VOID,		JSDOCSTR("value [,value]")
    	,JSDOCSTR("display one or more values as raw strings (may include NULs)")
    	,310
    	},
    	{"writeln",			js_writeln,			1, JSTYPE_VOID,		JSDOCSTR("value [,value]")
    	,JSDOCSTR("display one or more values as raw strings followed by a single carriage-return/line-feed pair (new-line)")
    	,315
    	},
    	{"putmsg",			js_putmsg,			1, JSTYPE_VOID,		JSDOCSTR("text [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]")
    	,JSDOCSTR("display message text (Ctrl-A codes, @-codes, pipe codes, etc.).<br> "
    		"See <tt>P_*</tt> in <tt>sbbsdefs.js</tt> for <i>mode</i> flags.<br>"
    		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known.<br>"
    		"When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.")
    	,310
    	},
    	{"center",			js_center,			1, JSTYPE_VOID,		JSDOCSTR("text [,width]")
    	,JSDOCSTR("display a string centered on the screen, with an optionally-specified screen width (in columns)")
    	,310
    	},
    	{"wide",			js_wide,			1, JSTYPE_VOID,		JSDOCSTR("text")
    	,JSDOCSTR("display a string double-wide on the screen (sending \"fullwidth\" Unicode characters when possible)")
    	,31702
    	},
    	{"strlen",			js_strlen,			1, JSTYPE_NUMBER,	JSDOCSTR("text [,mode=<tt>P_NONE</tt>]")
    	,JSDOCSTR("returns the printed-length (number of columns) of the specified <i>text</i>, accounting for Ctrl-A codes")
    	,310
    	},
    	{"printfile",		js_printfile,		1, JSTYPE_BOOLEAN,		JSDOCSTR("filename [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]")
    	,JSDOCSTR("print a message text file with optional mode.<br>"
    		"When <tt>P_WORDWRAP</tt> mode flag is specified, <i>orig_columns</i> specifies the original text column width, if known.<br>"
    		"When <i>scope</i> is specified, <tt>@JS:property@</tt> codes will expand the referenced property names.")
    	,310
    	},
    	{"printtail",		js_printtail,		2, JSTYPE_BOOLEAN,		JSDOCSTR("filename, lines [,mode=<tt>P_NONE</tt>] [,orig_columns=0] [,object scope]")
    	,JSDOCSTR("print the last <i>n</i> lines of file with optional mode, original column width, and scope.")
    	,310
    	},
    	{"editfile",		js_editfile,		1, JSTYPE_BOOLEAN,		JSDOCSTR("filename")
    	,JSDOCSTR("edit/create a text file using the user's preferred message editor")
    	,310
    	},
    	{"uselect",			js_uselect,			0, JSTYPE_NUMBER,	JSDOCSTR("[number, title, item] [,ars]")
    	,JSDOCSTR("user selection menu, call for each item, then with no args to display select menu")
    	,312
    	},
    	{"saveline",		js_saveline,		0, JSTYPE_BOOLEAN,	JSDOCSTR("")
    	,JSDOCSTR("push the current console line of text and attributes to a (local) LIFO list of //saved lines//")
    	,310
    	},
    	{"restoreline",		js_restoreline,		0, JSTYPE_BOOLEAN,	JSDOCSTR("")
    	,JSDOCSTR("pop the most recently //saved line// of text and attributes and display it on the remote console")
    	,310
    	},
    	{"ansi",			js_ansi,			1, JSTYPE_STRING,	JSDOCSTR("attribute [,current_attribute]")
    	,JSDOCSTR("returns ANSI sequence required to generate specified terminal <i>attribute</i> "
    	"(e.g. <tt>YELLOW|HIGH|BG_BLUE</tt>), "
    	"if <i>current_attribute</i> is specified, an optimized ANSI sequence may be returned")
    	,310
    	},
    	{"ansi_save",		js_pushxy,			0, JSTYPE_ALIAS	},
    	{"ansi_pushxy",		js_pushxy,			0, JSTYPE_ALIAS	},
    	{"pushxy",			js_pushxy,			0, JSTYPE_VOID,		JSDOCSTR("")
    	,JSDOCSTR("save the current cursor position (x and y coordinates) in the remote terminal")
    	,311
    	},
    	{"ansi_restore",	js_popxy,			0, JSTYPE_ALIAS },
    	{"ansi_popxy",		js_popxy,			0, JSTYPE_ALIAS },
    	{"popxy",			js_popxy,			0, JSTYPE_VOID,		JSDOCSTR("")
    	,JSDOCSTR("restore a saved cursor position to the remote terminal (requires terminal support, e.g. ANSI)")
    	,311
    	},
    	{"ansi_gotoxy",		js_gotoxy,			1, JSTYPE_ALIAS },
    	{"gotoxy",			js_gotoxy,			1, JSTYPE_VOID,		JSDOCSTR("[x,y] or [object { x,y }]")
    	,JSDOCSTR("move cursor to a specific screen coordinate (ANSI or PETSCII, 1-based values), "
    	"arguments can be separate x and y coordinates or an object with x and y properties "
    	"(like that returned from <tt>console.getxy()</tt>)")
    	,311
    	},
    	{"ansi_up",			js_cursor_up,		0, JSTYPE_ALIAS },
    	{"up",				js_cursor_up,		0, JSTYPE_VOID,		JSDOCSTR("[rows=<tt>1</tt>]")
    	,JSDOCSTR("move cursor up one or more rows")
    	,311
    	},
    	{"ansi_down",		js_cursor_down,		0, JSTYPE_ALIAS },
    	{"down",			js_cursor_down,		0, JSTYPE_VOID,		JSDOCSTR("[rows=<tt>1</tt>]")
    	,JSDOCSTR("move cursor down one or more rows")
    	,311
    	},
    	{"ansi_right",		js_cursor_right,	0, JSTYPE_ALIAS },
    	{"right",			js_cursor_right,	0, JSTYPE_VOID,		JSDOCSTR("[columns=<tt>1</tt>]")
    	,JSDOCSTR("move cursor right one or more columns")
    	,311
    	},
    	{"ansi_left",		js_cursor_left,		0, JSTYPE_ALIAS },
    	{"left",			js_cursor_left,		0, JSTYPE_VOID,		JSDOCSTR("[columns=<tt>1</tt>]")
    	,JSDOCSTR("move cursor left one or more columns")
    	,311
    	},
    	{"ansi_getlines",	js_getlines,		0, JSTYPE_ALIAS },
    	{"getlines",		js_getlines,		0, JSTYPE_ALIAS },
    	{"getdimensions",	js_getlines,		0, JSTYPE_VOID,		JSDOCSTR("")
    	,JSDOCSTR("query the number of rows and columns on the remote terminal")
    	,311
    	},
    	{"ansi_getxy",		js_getxy,			0, JSTYPE_ALIAS },
    	{"getxy",			js_getxy,			0, JSTYPE_OBJECT,	JSDOCSTR("")
    	,JSDOCSTR("query the current cursor position on the remote (ANSI) terminal "
    		"and returns the coordinates as an object (with <tt>x</tt> and <tt>y</tt> properties) or <tt>false</tt> on failure")
    	,311
    	},
    	{"lock_input",		js_lock_input,		1, JSTYPE_VOID,		JSDOCSTR("[lock=<tt>true</tt>]")
    	,JSDOCSTR("lock the user input thread (allowing direct client socket access)")
    	,310
    	},
    	{"telnet_cmd",		js_telnet_cmd,		3, JSTYPE_BOOLEAN,		JSDOCSTR("command [,option=<tt>0</tt>] [,timeout=<tt>0</tt>]")
    	,JSDOCSTR("send Telnet command (with optional command option) to remote client"
    		", if the optional timeout is specified (in milliseconds, added in v3.17), then an acknowledgment will be expected and"
    		" the return value will indicate whether or not one was received")
    	,317
    	},
    	{"handle_ctrlkey",	js_handle_ctrlkey,	1, JSTYPE_BOOLEAN,	JSDOCSTR("key [,mode=<tt>K_NONE</tt>]")
    	,JSDOCSTR("call internal control key handler for specified control key, returns <tt>true</tt> if handled")
    	,311
    	},
    	{"term_supports",	js_term_supports,	1, JSTYPE_BOOLEAN,	JSDOCSTR("[terminal_flags]")
    	,JSDOCSTR("either returns <i>bool</i>, indicating whether or not the current user/client "
    		"supports all the specified <i>terminal_flags</i>, or returns the current user/client's "
    		"<i>terminal_flags</i> (numeric bit-field) if no <i>terminal_flags</i> were specified")
    	,314
    	},
    	{"term_updated",	js_term_updated,	1, JSTYPE_BOOLEAN,	JSDOCSTR("")
    	,JSDOCSTR("update the node's <tt>terminal.ini</tt> file to match the current terminal settings")
    	,31802
    	},
    	{"backspace",		js_backspace,		0, JSTYPE_VOID,		JSDOCSTR("[count=<tt>1</tt>]")
    	,JSDOCSTR("send a destructive backspace sequence")
    	,315
    	},
    	{"creturn",			js_creturn,			0, JSTYPE_VOID,		JSDOCSTR("[count=<tt>1</tt>]")
    	,JSDOCSTR("send a carriage return sequence")
    	,31700
    	},
    	{"clearkeybuffer",	js_clearkeybuf,		0, JSTYPE_VOID,		JSDOCSTR("")
    	,JSDOCSTR("clear keyboard input buffer")
    	,315
    	},
    	{"getbyte",			js_getbyte,			1, JSTYPE_NUMBER,	JSDOCSTR("[timeout=<tt>0</tt>]")
    	,JSDOCSTR("returns an unprocessed input byte from the remote terminal "
    		"with optional <i>timeout</i> in milliseconds (defaults to 0, for no wait), "
    		"returns <i>null</i> on failure (timeout)")
    	,31700
    	},
    	{"putbyte",			js_putbyte,			1, JSTYPE_BOOLEAN,	JSDOCSTR("value")
    	,JSDOCSTR("sends an unprocessed byte value to the remote terminal")
    	,31700
    	},
    	{"add_hotspot",		js_add_hotspot,		1,	JSTYPE_VOID,	JSDOCSTR("cmd [,bool hungry=<tt>true</tt>] [,min_x] [,max_x] [,y]")
    		,JSDOCSTR("adds a mouse hot-spot (a clickable screen area that generates keyboard input)")
    		,31800
    		},
    	{"clear_hotspots",	js_clear_hotspots,		0,	JSTYPE_VOID,	JSDOCSTR("")
    		,JSDOCSTR("clear all current mouse hot-spots")
    		,31800
    		},
    	{"scroll_hotspots",	js_scroll_hotspots,		0,	JSTYPE_VOID,	JSDOCSTR("[rows=1]")
    		,JSDOCSTR("scroll all current mouse hot-spots by the specific number of rows")
    		,31800
    		},
    	{0}
    };
    
    
    static JSBool js_console_resolve(JSContext *cx, JSObject *obj, jsid id)
    {
    	char*			name=NULL;
    	JSBool			ret;
    
    	if(id != JSID_VOID && id != JSID_EMPTY) {
    		jsval idval;
    
    		JS_IdToValue(cx, id, &idval);
    		if(JSVAL_IS_STRING(idval)) {
    			JSSTRING_TO_MSTRING(cx, JSVAL_TO_STRING(idval), name, NULL);
    			HANDLE_PENDING(cx, name);
    		}
    	}
    
    	ret=js_SyncResolve(cx, obj, name, js_console_properties, js_console_functions, NULL, 0);
    	if(name)
    		free(name);
    	return ret;
    }
    
    static JSBool js_console_enumerate(JSContext *cx, JSObject *obj)
    {
    	return(js_console_resolve(cx, obj, JSID_VOID));
    }
    
    JSClass js_console_class = {
         "Console"				/* name			*/
        ,JSCLASS_HAS_PRIVATE	/* flags		*/
    	,JS_PropertyStub		/* addProperty	*/
    	,JS_PropertyStub		/* delProperty	*/
    	,js_console_get			/* getProperty	*/
    	,js_console_set			/* setProperty	*/
    	,js_console_enumerate	/* enumerate	*/
    	,js_console_resolve		/* resolve		*/
    	,JS_ConvertStub			/* convert		*/
    	,JS_FinalizeStub		/* finalize		*/
    };
    
    JSObject* js_CreateConsoleObject(JSContext* cx, JSObject* parent)
    {
    	JSObject*	obj;
    	sbbs_t*		sbbs;
    
    	if((sbbs=(sbbs_t*)JS_GetContextPrivate(cx))==NULL)
    		return(NULL);
    
    	if((obj=JS_DefineObject(cx, parent, "console", &js_console_class, NULL
    		,JSPROP_ENUMERATE|JSPROP_READONLY))==NULL)
    		return(NULL);
    
    	if (obj == NULL)
    		return NULL;
    
    	JS_SetPrivate(cx, obj, sbbs);
    
    	/* Create an array of pre-defined colors */
    
    	JSObject* color_list;
    
    	if((color_list=JS_NewArrayObject(cx,0,NULL))==NULL)
    		return(NULL);
    
    	if(!JS_DefineProperty(cx, obj, "color_list", OBJECT_TO_JSVAL(color_list)
    		,NULL, NULL, 0))
    		return(NULL);
    
    	for(uint i=0;i<sbbs->cfg.total_colors;i++) {
    
    		jsval val=INT_TO_JSVAL(sbbs->cfg.color[i]);
    		if(!JS_SetElement(cx, color_list, i, &val))
    			return(NULL);
    	}
    
    #ifdef BUILD_JSDOCS
    	js_DescribeSyncObject(cx,obj,"Controls the remote terminal",310);
    	js_CreateArrayOfStrings(cx, obj, "_property_desc_list", con_prop_desc, JSPROP_READONLY);
    #endif
    
    	return(obj);
    }
    
    #endif	/* JAVSCRIPT */