dorkit.js 15.28 KiB
js.load_path_list.unshift(js.exec_dir+"/dorkit/");
if (js.global.system !== undefined)
js.load_path_list.unshift(system.exec_dir+"/dorkit/");
load("attribute.js");
load("graphic.js");
var dk = {
console:{
last_pos:{x:1, y:1},
key:{
CTRL_A:'\x01',
CTRL_B:'\x02',
CTRL_C:'\x03',
CTRL_D:'\x04',
CTRL_E:'\x05',
CTRL_F:'\x06',
CTRL_G:'\x07',
BEEP:'\x07',
CTRL_H:'\x08',
BACKSPACE:'\x08',
CTRL_I:'\x09',
TAB:'\x09',
CTRL_J:'\x0a',
LF:'\x0a',
CTRL_K:'\x0b',
CTRL_L:'\x0c',
CLEAR:'\x0c',
CTRL_M:'\x0d',
RETURN:'\x0d',
CTRL_N:'\x0e',
CTRL_O:'\x0f',
CTRL_P:'\x10',
CTRL_Q:'\x11',
CTRL_R:'\x12',
CTRL_S:'\x13',
PAUSE:'\x13',
CTRL_T:'\x14',
CTRL_U:'\x15',
CTRL_V:'\x16',
CTRL_W:'\x17',
CTRL_X:'\x18',
CTRL_Y:'\x19',
CTRL_Z:'\x1a',
ESCAPE:'\x1b',
DELETE:'\x7f',
/* End of ASCII */
/* Start of extended characters */
KEY_UP:'KEY_UP',
KEY_DOWN:'KEY_DOWN',
KEY_RIGHT:'KEY_RIGHT',
KEY_LEFT:'KEY_LEFT',
KEY_HOME:'KEY_HOME',
KEY_END:'KEY_END',
KEY_F1:'KEY_F1',
KEY_F2:'KEY_F2',
KEY_F3:'KEY_F3',
KEY_F4:'KEY_F4',
KEY_F5:'KEY_F5',
KEY_F6:'KEY_F6',
KEY_F7:'KEY_F7',
KEY_F8:'KEY_F8',
KEY_F9:'KEY_F9',
KEY_F10:'KEY_F10',
KEY_F11:'KEY_F11',
KEY_F12:'KEY_F12',
KEY_PGUP:'KEY_PGUP',
KEY_PGDOWN:'KEY_PGDOWN',
KEY_INS:'KEY_INS',
KEY_DEL:'KEY_DEL',
POSITION_REPORT:'POSITION_REPORT'
},
x:1, // Current column (1-based)
y:1, // Current row (1-based)
attr:new Attribute(7), // Current attribute
ansi:true, // ANSI support is enabled
charset:'cp437', // Supported character set
local:true, // True if writes should go to the local screen
remote:true, // True if writes should go to the remote terminal
rows:undefined, // Rows in users terminal
cols:undefined, // Columns in users terminal
keybuf:'',
/*
* Returns a string with ^A codes converted to ANSI or stripped
* as appropriate.
*/
parse_ctrla:function(txt, orig_attr) {
var ret='';
var i;
var curr_attr;
var next_attr;
if (orig_attr !== undefined)
curr_attr = new Attribute(orig_attr);
next_attr = new Attribute(curr_attr);
function attr_str() {
var ansi_str;
if (curr_attr === undefined || curr_attr.value != next_attr.value) {
ansi_str = next_attr.ansi(curr_attr);
if (curr_attr === undefined)
curr_attr = new Attribute(next_attr);
else
curr_attr.value = next_attr.value;
return ansi_str;
}
return '';
}
for (i=0; i<txt.length; i++) {
if (txt.charCodeAt(i)==1) {
i++;
switch(txt.substr(i, 1)) {
case '\1':
ret += attr_str()+'\1';
break;
case 'K':
next_attr.fg = Attribute.BLACK;
break;
case 'R':
next_attr.fg = Attribute.RED;
break;
case 'G':
next_attr.fg = Attribute.GREEN;
break;
case 'Y':
next_attr.fg = Attribute.YELLOW;
break;
case 'B':
next_attr.fg = Attribute.BLUE;
break;
case 'M':
next_attr.fg = Attribute.MAGENTA;
break;
case 'C':
next_attr.fg = Attribute.CYAN;
break;
case 'W':
next_attr.fg = Attribute.WHITE;
break;
case '0':
next_attr.bg = Attribute.BLACK;
break;
case '1':
next_attr.bg = Attribute.RED;
break;
case '2':
next_attr.bg = Attribute.GREEN;
break;
case '3':
next_attr.bg = Attribute.YELLOW;
break;
case '4':
next_attr.bg = Attribute.BLUE;
break;
case '5':
next_attr.bg = Attribute.MAGENTA;
break;
case '6':
next_attr.bg = Attribute.CYAN;
break;
case '7':
next_attr.bg = Attribute.WHITE;
break;
case 'H':
next_attr.bright = true;
break;
case 'I':
next_attr.blink = true;
break;
case 'N':
next_attr.value = 7;
break;
case '-':
if (next_attr.blink || next_attr.bright || next_attr.bg !== Attribute.BLACK)
next_attr.value = 7;
break;
case '_':
if (next_attr.blink || next_attr.bg !== Attribute.BLACK)
next_attr.value = 7;
break;
default:
break;
}
}
else {
ret += attr_str() + txt.substr(i, 1);
}
}
return ret + attr_str();
},
/*
* Clears the current screen to black and moves to location 1,1
* sets the current attribute to 7
*/
clear:function() {
if (this.local)
this.local_io.clear();
if (this.remote)
this.remote_io.clear();
},
/*
* Clears to end of line.
* Not available without ANSI (???)
*/
cleareol:function() {
if (this.local)
this.local_io.cleareol();
if (this.remote)
this.remote_io.cleareol();
},
/*
* Moves the cursor to the specified position.
* returns false on error.
* Not available without ANSI
*/
gotoxy:function(x,y) {
if (this.local)
this.local_io.gotoxy(x,y);
if (this.remote)
this.remote_io.gotoxy(x,y);
},
/*
* Returns a Graphic object representing the specified block
* or undefined on error (ie: invalid block specified).
*/
getblock:function(sx,sy,ex,ey) {
},
/*
* Writes a string unmodified.
* TODO: This needs to parse ANSI and set attr...
*/
print:function(string) {
var m;
if (this.local)
this.local_io.print(string);
if (this.remote)
this.remote_io.print(string);
},
/*
* Writes a string with a "\r\n" appended.
*/
println:function(line) {
this.print(line+'\r\n');
},
/*
* Writes a string after parsing ^A codes.
*/
aprint:function(string) {
this.println(this.parse_ctrla(line));
},
/*
* Writes a string after parsing ^A codes and appends a "\r\n".
*/
aprintln:function(line) {
this.println(this.parse_ctrla(line));
},
/*
* Waits up to timeout millisections and returns true if a key
* is pressed before the timeout. For ANSI sequences, returns
* true when the entire ANSI sequence is available.
*/
waitkey:function(timeout) {
var q = new Queue("dorkit_input");
if (q.poll(timeout) === false)
return false;
return true;
},
/*
* Returns a single *KEY*, ANSI is parsed to a single key.
* Returns undefined if there is no key pressed.
*/
getkey:function() {
var ret;
var m;
if (this.keybuf.length > 0) {
var ret = this.keybuf.substr(0,1);
this.keybuf = this.keybuf.substr(1);
return ret;
}
if (!this.waitkey(0))
return undefined;
var q = new Queue("dorkit_input");
ret = q.read();
if (ret.length > 1) {
if (ret.substr(0, 9) === 'POSITION_') {
m = ret.match(/^POSITION_([0-9]+)_([0-9]+)/);
if (m == null)
return undefined;
this.last_pos.x = parseInt(m[2], 10);
this.last_pos.y = parseInt(m[1], 10);
ret = 'POSITION_REPORT';
}
ret=ret.replace(/\x00.*$/,'');
}
return ret;
},
/*
* Returns a single byte... ANSI is not parsed.
*/
getbyte:function() {
if (this.keybuf.length > 0) {
var ret = this.keybuf.substr(0,1);
this.keybuf = this.keybuf.substr(1);
return ret;
}
if (!this.waitkey(0))
return undefined;
var q = new Queue("dorkit_input");
ret = q.read();
if (ret.length > 1) {
ret=this.key[ret.replace(/^.*\x00/,'')];
this.keybuf = ret.substr(1);
ret = ret.substr(0,1);
}
return ret;
},
getstr_defaults:{
password:false, // Password field (echo password_char instead of string)
password_char:'*', // Character to echo when entering passwords.
upper_case:false, // Convert to upper-case during editing
integer:false, // Input integer values only
decimal:false, // Input decimal values only
ansi:false, // Allows ANSI input
ctrl_a:false, // Allows CTRL-A input
beep:false, // Allows beep input (WTF?)
edit:'', // Edit this value rather than a new input
crlf:true, // Print CRLF after input
exascii:false, // Allow extended ASCII (Higher than 127)
echo:true, // Display input while typing
input_box:false, // Draw an input "box" in a different background attr
select:true, // Select all when editing... first character typed if not movement will erase
attr:undefined, // Foreground attribute... used to draw the input box and for output... undefined means "use current"
sel_attr:undefined, // Selected text attribute... used for edit value when select is true. Undefined uses inverse (swaps fg and bg, leaving bright and blink unswapped)
len:80 // Max length and length of input box
},
getstr:function(in_opts) {
var i;
var opt={};
var str;
var orig_attr = new Attribute(this.attr);
// Set up option defaults
for (i in this.getstr_defaults) {
if (in_opts[i] !== undefined)
opt[i] = in_opts[i];
else
opt[i] = this.getstr_defaults[i];
}
if (opt.attr === undefined)
opt.attr=new Attribute(this.attr);
if (opt.sel_attr === undefined) {
opt.sel_attr=new Attribute(this.attr);
i = opt.sel_attr.fg;
opt.sel_attr.fg = opt.sel_attr.bg;
opt.sel_attr.bg = i;
}
str = opt.edit;
// Draw the input box...
},
},
connection:{
type:undefined,
baud:undefined,
parity:undefined,
node:undefined,
dte:undefined,
error_correcting:true,
time:undefined,
socket:undefined,
telnet:false
},
user:{
full_name:undefined,
location:undefined,
home_phone:undefined,
work_phone:undefined,
// Just a copy of work_phone when using door.sys
data_phone:undefined,
pass:undefined,
level:undefined,
times_on:undefined,
last_called:undefined,
// These need getter/setters
seconds_remaining:undefined,
minutes_remaining:undefined,
conference:[],
curr_conference:undefined,
expires:undefined,
number:undefined,
default_protocol:undefined, // Default transfer protocol... X, Y, Z, etc.
uploads:undefined,
upload_kb:undefined,
downloads:undefined,
download_kb:undefined,
kb_downloaded_today:undefined,
max_download_kb_per_day:undefined,
birthdate:undefined,
alias:undefined,
ansi_supported:undefined,
time_credits:undefined,
last_new_file_scan_date:undefined,
last_call_time:undefined,
max_daily_files:undefined,
downloaded_today:undefined,
comment:undefined,
doors_opened:undefined,
messages_left:undefined,
expert_mode:true
},
system:{
main_dir:undefined,
gen_dir:undefined,
sysop_name:undefined,
default_attr:undefined,
mode:(js.global.bbs !== undefined
&& js.global.server !== undefined
&& js.global.client !== undefined
&& js.global.user !== undefined
&& js.global.console !== undefined) ? 'sbbs'
: (js.global.jsexec_revision !== undefined ? 'jsexec'
: (js.global.jsdoor_revision !== undefined ? 'jsdoor' : undefined))
},
misc:{
event_time:undefined,
record_locking:undefined
},
detect_ansi:function() {
var start = Date.now();
this.console.remote_io.print("\x1b[s" + // Save cursor position.
"\x1b[255B" + // Locate as far down as possible
"\x1b[255C" + // Locate as far right as possible
"\b "+ // Print something (some terminals apparently need this)
"\x1b[6n" + // Get cursor position
"\x1b[u" // Restore cursor position
);
while(Date.now() - start < 500) {
if(this.console.waitkey(500)) {
if (this.console.getkey() == this.console.key.POSITION_REPORT) {
// TODO: Should we trust the drop file on number of rows?
this.console.cols = this.console.last_pos.x;
this.console.rows = this.console.last_pos.y;
this.console.ansi = true;
return true;
}
}
}
return false;
},
parse_dropfile:function(path) {
var f = new File(path);
var df;
if (!f.open("r"))
return false;
df = f.readAll();
f.close();
if (df.length != 52)
return false;
this.connection.type = df[0];
this.connection.baud = parseInt(df[1], 10);
this.connection.parity = parseInt(df[2], 10);
this.connection.node = parseInt(df[3], 10);
this.connection.dte = parseInt(df[4], 10);
if (df[5].toUpperCase() == 'N')
this.local = false;
// TODO: Some bools ignored here...
this.user.full_name = df[9];
this.user.location = df[10];
this.user.home_phone = df[11];
this.user.work_phone = df[12];
this.user.data_phone = df[12];
this.user.pass = df[13];
this.user.level = parseInt(df[14], 10);
this.user.times_on = parseInt(df[15], 10);
// TODO: Parse a date out of this.
this.user.last_called = df[16];
this.user.seconds_remaining = parseInt(df[17], 10);
this.user.minutes_remaining = parseInt(df[18], 10);
switch(df[19].toUpperCase()) {
case 'GR':
this.ansi = true;
this.codepage = 'cp437';
break;
case 'NG':
this.ansi = false;
this.codepage = 'cp437';
break;
default: // ie: '7E'
this.ansi = false;
this.codepage = '7-bit';
break;
}
this.rows = parseInt(df[20], 10);
this.user.expert_mode = (df[21].toUpperCase === 'Y') ? true : false;
this.user.conference = df[22].split(/\s*,\s*/);
this.user.curr_conference = parseInt(df[23]);
// TODO: Parse a date out of this.
this.user.expires = df[24];
this.user.number = parseInt(df[25], 10);
this.user.default_protocol = df[26];
this.user.uploads = parseInt(df[27], 10);
this.user.downloads = parseInt(df[28], 10);
this.user.kb_downloaded_today = parseInt(df[29], 10);
this.user.max_download_kb_per_day = parseInt(df[30], 10);
// TODO: Parse a date out of this.
this.user.birthdate = df[31];
this.system.main_dir = df[32];
this.system.gen_dir = df[33];
this.system.sysop_name = df[34];
this.user.alias = df[35];
// TODO: Parse a timestamp thingie
this.misc.event_time = df[36];
this.connection.error_correcting = (df[37].toUpperCase === 'N') ? false : true;
if (this.ansi == true)
this.user.ansi_supported = true;
else
this.user.ansi_supported = (df[38].toUpperCase === 'Y') ? true : false;
this.misc.record_locking = (df[39].toUpperCase === 'N') ? false : true;
this.system.default_attr = new Attribute(parseInt(df[40], 10));
this.user.time_credits = parseInt(df[41], 10);
// TODO: Parse a date out of this.
this.user.last_new_file_scan_date = df[42];
// TODO: Parse a timestamp thingie
this.connection.time = df[43];
// TODO: Parse a timestamp thingie
this.user.last_call_time = df[44];
this.user.max_daily_files = parseInt(df[45], 10);
this.user.downloaded_today = parseInt(df[46], 10);
this.user.upload_kb = parseInt(df[47], 10);
this.user.download_kb = parseInt(df[48], 10);
this.user.comment = df[49];
this.user.doors_opened = parseInt(df[50], 10);
this.user.messages_left = parseInt(df[50], 10);
},
parse_cmdline:function(argc, argv) {
var i;
for (i=0; i<argc; i++) {
switch(argv[i]) {
case '-t':
case '-telnet':
this.connection.telnet = true;
break;
case '-s':
case '-socket':
if (i+1 < argc)
this.connection.socket = argv[++i];
break;
case '-l':
case '-local':
this.console.local = true;
this.console.remote = false;
break;
}
}
if (this.connection.telnet === undefined)
this.connection.telnet = false;
}
};
load("local_console.js");
dk.parse_cmdline(argc, argv);
if (dk.connection.socket !== undefined)
dk.system.mode = 'socket';
switch(dk.system.mode) {
case 'sbbs':
load("sbbs_console.js");
break;
case 'jsexec':
load("jsexec_console.js");
break;
case 'jsdoor':
load("jsexec_console.js");
break;
case 'socket':
load("socket_console.js", dk.connection.socket, dk.connection.telnet);
break;
}