const SESSION_VIEWDATA = (1<<2); var SESSION_EXT = 'vtx'; var FRAME_WIDTH = 40; var FRAME_HEIGHT = 22; var FRAME_PROVIDER_LENGTH = 23; var FRAME_PAGE_LENGTH = 11; var FRAME_COST_LENGTH = 6; var FRAME_ATTR_LENGTH = 1; // Space that an attribute takes const VIEWDATA_LEFT = '\x08'; const VIEWDATA_RIGHT = '\x09'; const VIEWDATA_DOWN = '\x0a'; // \n const VIEWDATA_UP = '\x0b'; const VIEWDATA_CLS = '\x0c'; const VIEWDATA_CR = '\x0d'; // \r const VIEWDATA_CON = '\x11'; const VIEWDATA_COFF = '\x14'; const VIEWDATA_HOME = '\x1e'; const VIEWDATA_BLINK = '\x48'; const VIEWDATA_STEADY = '\x49'; const VIEWDATA_NORMAL = '\x4c'; const VIEWDATA_DOUBLE = '\x4d'; const VIEWDATA_CONCEAL = '\x58'; const VIEWDATA_BLOCKS = '\x59'; const VIEWDATA_SEPARATED = '\x5a'; const VIEWDATA_BLACKBACK = '\x5c'; const VIEWDATA_NEWBACK = '\x5d'; const VIEWDATA_HOLD = '\x5e'; const VIEWDATA_REVEAL = '\x5f'; const VIEWDATA_RED = '\x41'; const VIEWDATA_GREEN = '\x42'; const VIEWDATA_YELLOW = '\x43'; // C const VIEWDATA_BLUE = '\x44'; const VIEWDATA_MAGENTA = '\x45'; const VIEWDATA_CYAN = '\x46'; const VIEWDATA_WHITE = '\x47'; const VIEWDATA_MOSIAC_RED = '\x51'; const VIEWDATA_MOSIAC_GREEN = '\x52'; const VIEWDATA_MOSIAC_YELLOW = '\x53'; const VIEWDATA_MOSIAC_BLUE = '\x54'; const VIEWDATA_MOSIAC_MAGENTA = '\x55'; const VIEWDATA_MOSIAC_CYAN = '\x56'; const VIEWDATA_MOSIAC_WHITE = '\x57'; // W /* BINARY DUMP LEVEL 1 ATTRIBUTES */ const VIEWDATA_BIN_RED = '\x01'; const VIEWDATA_BIN_GREEN = '\x02'; const VIEWDATA_BIN_YELLOW = '\x03'; const VIEWDATA_BIN_BLUE = '\x04'; const VIEWDATA_BIN_MAGENTA = '\x05'; const VIEWDATA_BIN_CYAN = '\x06'; const VIEWDATA_BIN_WHITE = '\x07'; /** * ViewData characters are 7bit (0x00-0x7f) * * Chars 0x00-0x1f are control characters (display attributes) and are sent to the terminal with 0x1b * + 0x00-0x07 are foreground colors * + 0x08-0x09 flash/steady * + 0x0a-0x0b end/start box (?) * * + 0x0c-0x0d normal/double height * + 0x0e-0x0f double width (?) * * + 0x10-0x17 are foreground graphics (mosiac) colors * + 0x18/0x1f conceal/reveal * + 0x19-0x1a solid/seperated graphics * + 0x1b unused * + 0x1c-1x1d Black/New Background (new background converts color foreground to background) * + 0x1e-0x1f graphics hold/release (enables changing color and repeats previous graphics char) * Chars 0x20-0x7f are normal printed ASCII chars * Chars 0x20-0x3f & 0x60-0x7f when activated with a MOSIAC color sends a 2x3 pixel character * * We can map these into cga_defs with the following amendments: * 0x00-0x0f = foreground/background colors (4 bits) (8 foreground/8 background colors) * 0x10 - mosiac (bit 4) * 0x20 - conceal (bit 5) * 0x40 - seperated graphics (bit 6) * 0x80 - flash (bit 7) * 0x100 - double height (bit 8) * 0x200 - hold (bit 9) * 0x400 - new background (bits 10/11) * 0x800 - black background (bits 10/11) * 0xc00 - unused (bits 10/11) * bits (12-15) unused * * @type {number} */ var MOSIAC = 0x10; // Toggles var CONCEAL = 0x20; var REVEAL = 0x2000; // @temp Turns off Conceal var SEPARATED = 0x40; var BLOCKS = 0x4000; // @temp Turns off Separated var STEADY = 0x8000; // @temp (turn off flash) var DOUBLE = 0x100; var NORMAL = 0x1000; // @temp Turns off Double Height var HOLD = 0x200; var RELEASE = 0x20000; // @temp turns off Hold var NEWBACK = 0x400; var BLACKBACK = 0x800; /** * This function converts ANSI text into an array of attributes * * @param contents - Our ANSI content to convert * @param width - The width before wrapping to the next line * @param yoffset - fields offset as discovered * @param xoffset - fields offset as discovered * @param debug - Enable debug mode */ function rawtoattrs(contents,width,yoffset,xoffset,debug) { if (debug) writeln('DEBUG active: '+debug); lines = (''+contents).split(/\r\n/); var i = 0; var bg = BG_BLACK; var fg = LIGHTGRAY; var attr = fg + bg + i; // Attribute state on a new line var new_line = attr; var y = 0; var frame = { content: [], dynamic_fields: [], input_fields: [], }; // @todo temp hack, rework ansi variable - perhaps have a function that converts an attribute back into an ANSI sequence var ansi = { i: 0, f: 37, b: 40 }; while (lines.length > 0) { var x = 0; var line = lines.shift(); if ((debug !== undefined) && (y > debug)) { exit(1); } if (debug) { log(LOG_DEBUG,'y:'+y); log(LOG_DEBUG,'line:'+line); write('y:'+y+', line:'+line); } while (line.length > 0) { if (x >= width) { x = 0; // Each new line, we reset the attrs attr = new_line; y++; } //writeln('next ch:'+line[0].charCodeAt(0)); /* parse control codes */ var m = line.match(/^([\x00-\x1f])/); if (m !== null) { line = line.substr(m[0].length); attr = 0; match = m.shift().charCodeAt(0); /* writeln('- match:'+match); writeln('- match 0x0f:'+(match & 0x0f)); if (match & 0x10) { writeln(' - got mosiac'); attr += MOSIAC; } */ //if (match < 0x0f) { //switch(match & 0x07) { switch(match) { case 0x00: attr += BLACK; break; case 0x01: attr += RED; break; case 0x02: attr += GREEN; break; case 0x03: attr += YELLOW; break; case 0x04: attr += BLUE; break; case 0x05: attr += MAGENTA; break; case 0x06: attr += CYAN; break; case 0x07: attr += LIGHTGRAY; break; case 0x08: attr = BLINK; break; case 0x09: attr = STEADY; break; /* case 0x0a: //attr = ENDBOX; // End Box (Unused?) break; case 0x0b: //attr = STARTBOX; // Start Box (Unused?) break; */ case 0x0c: //attr &= ~DOUBLE; attr = NORMAL; break; case 0x0d: attr = DOUBLE; break; case 0x0e: attr = NORMAL; // @todo Double Width (Unused)? break; case 0x0f: attr = NORMAL; // @todo Double Width (Unused?) break; case 0x10: attr = MOSIAC|BLACK; break; case 0x11: attr += MOSIAC|RED; break; case 0x12: attr += MOSIAC|GREEN; break; case 0x13: attr += MOSIAC|YELLOW; break; case 0x14: attr += MOSIAC|BLUE; break; case 0x15: attr += MOSIAC|MAGENTA; break; case 0x16: attr += MOSIAC|CYAN; break; case 0x17: attr += MOSIAC|LIGHTGRAY; break; case 0x18: attr = CONCEAL; break; case 0x19: attr = BLOCKS; break; case 0x1a: attr = SEPARATED; break; /* case 0x1b: //attr = NORMAL; // CSI break; */ case 0x1c: attr = BLACKBACK; // Black Background break; case 0x1d: attr = NEWBACK; // New Background break; case 0x1e: attr = HOLD; // Mosiac Hold break; case 0x1f: attr = RELEASE; // Mosiac Release break; // Catch all for other codes default: attr = 0xff00; } if (debug) writeln(' - got control code:'+attr+'['+y+','+x+'] - length:'+attr.length); store(x++,y,null,attr); attr = undefined; continue; } /* parse an input field */ // Input field 'FIELD;valueTYPE;input char' // @todo remove the trailing ESC \ to end the field, just use a control code ^B \x02 (Start of Text) and ^C \x03 var m = line.match(/^\x1b_(([A-Z]+;[0-9a-z]+)([;]?.+)?)\x1b\\/); if (m !== null) { log(LOG_DEBUG,'Got input field: '+JSON.stringify(m)); log(LOG_DEBUG,'ansi:'+JSON.stringify(ansi)); // full string that matched match = m.shift(); // thus, the rest of the line line = line.substr(match.length); //writeln('rest of line:'+JSON.stringify(line)); // We are interested in our field match var sos = m.shift().split(';'); //writeln('sos:'+JSON.stringify(sos)); for (var num in sos) { switch (num) { // First value is the field name case '0': field = sos[num]; break; // Second value is the length/type of the field, nnX nn=size in chars, T=type (lower case) case '1': var c = sos[num].match(/([0-9]+)([a-z])/); if (! c) { log(LOG_ERROR,'SOS FAILED PARSING FIELD LENGTH/TYPE. ['+r+'x'+c+'] '+sos[num]); break; } //log(LOG_DEBUG,'SOS ['+r+'x'+c+'] NUM CHARS: '+x[1]+', TYPE: '+x[2]); fieldlen = c[1]; fieldtype = c[2]; break; // Third field is the char to to use case '2': fieldchar = sos[num]; break; default: log(LOG_ERROR,'IGNORING ADDITIONAL SOS FIELDS. ['+r+'x'+c+'] '+sos[num]); } } // If we are padding our field with a char, we need to add that back to line // @todo validate if this goes beyond our width (and if scrolling not enabled) if (fieldlen) line = fieldchar.repeat(fieldlen)+line; frame.input_fields.push({ type: fieldtype, length: Number(fieldlen), char: fieldchar, name: field, attribute: JSON.parse(JSON.stringify(ansi)), x: Number(x+(xoffset !== undefined ? xoffset : 0)), y: Number(y+(yoffset !== undefined ? yoffset : 0)), value: '', }); log(LOG_DEBUG,'input_field:'+JSON.stringify(frame.input_fields.last)); } /* parse dynamic value field */ // @todo remove the trailing ESC \ to end the field, just use a control code ie: ^E \x05 (Enquiry) or ^Z \x26 (Substitute) var m = line.match(/^\x1bX(([a-zA-Z._:^;]+[0-9]?;-?[0-9^;]+)([;]?[^;]+)?)\x1b\\/); if (m !== null) { // full string that matched match = m.shift(); // thus, the rest of the line line = line.substr(match.length); //writeln('rest of line:'+JSON.stringify(line)); // We are interested in our field match var df = m.shift().split(';'); log(LOG_DEBUG,'- DF found at ['+x+'x'+y+'], Field: '+df[0]+', Length: '+df[1]+', Pad:'+df[2]); // If we are padding our field with a char, we need to add that back to line // @todo validate if this goes beyond our width (and if scrolling not enabled) line = (df[2] ? df[2] : '_').repeat(Math.abs(df[1]))+line; frame.dynamic_fields.push({ name: df[0], length: df[1], pad: df[2], x: x+(xoffset !== undefined ? xoffset : 0), y: y+(yoffset !== undefined ? yoffset : 0), value: undefined, }); } /* set character and attribute */ var ch = line[0]; line = line.substr(1); if (debug && (debug === y)) { writeln('y:'+y+', x:'+x+', ch:'+ch); } /* validate position */ if (y < 0) y = 0; if (x < 0) x = 0; store(x,y,ch,undefined); x++; } // Each new line, we reset the attrs attr = undefined; y++; } return frame; function store(x,y,ch,attr) { /* set character and attribute */ if (! frame.content[y+1]) frame.content[y+1]=[]; frame.content[y+1][x+1] = new Char(ch,attr,SESSION_EXT); } } load('ansitex/load/session.js'); // Our frame object function SessionProtocol() { Session.apply(this,arguments); this.settings.MSG_SENDORNOT = ascii(27)+'BKEY 1 TO SEND, 2 NOT TO SEND'; this.settings.MSG_LOGON = ascii(27)+'BKEY 1 TO LOGON, 2 TO RETURN'; this.settings.MSG_SENT = ascii(27)+'BMESSAGE SENT - KEY _ TO CONTINUE'; this.settings.MSG_NOTSENT = ascii(27)+'BMESSAGE NOT SENT - KEY _ TO CONTINUE'; this.settings.ERR_NO_PARENT = ascii(27)+'APARENT FRAME DOESNT EXIST'; this.settings.ERR_NOT_IMPLEMENTED = ascii(27)+'ANOT IMPLEMENTED YET?'; this.settings.ERR_ROUTE = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; this.settings.ERR_METHOD_NOT_EXIST = ascii(27)+'GMISTAKE?'+ascii(27)+'BTRY AGAIN OR TELL US ON *08'; this.settings.ACCESS_DENIED = ascii(27)+'AACCESS DENIED.'; this.settings.ALREADY_MEMBER = ascii(27)+'AALREADY MEMBER OF CUG' this.settings.INACTIVITY = ascii(27)+'AINACTIVITY ALERT, DISCONNECT PENDING...'; this.settings.INACTIVE = ascii(27)+'AINACTIVITY DISCONNECT'; this.settings.NOACTION = ascii(27)+'ANO ACTION PERFORMED'; this.settings.BASESTAR = ascii(27)+'B*'; this.settings.INVALID_CODE = ascii(27)+'AINVAID CODE, PLEASE TRY AGAIN **'; this.settings.TOKEN_EMAIL = ascii(27)+'ATOKEN EMAILED TO YOU...'; this.settings.TOKEN_SENT = ascii(27)+'ATOKEN SENT, PLEASE ENTER TOKEN'; this.settings.INVALID_EMAIL = ascii(27)+'AINVAID EMAIL, PLEASE TRY AGAIN *00'; this.settings.INVALID_UID = ascii(27)+'AINVAID USER ID, PLEASE TRY AGAIN *00'; this.settings.CANNOT_SEND_TOKEN = ascii(27)+'ACANNOT SEND VALIDATION CODE, PLEASE TRY AGAIN *00'; this.settings.USER_EXISTS = ascii(27)+'AERROR USER EXISTS, PLEASE TRY AGAIN *00'; this.settings.USER_CREATE_ERROR = ascii(27)+'AERROR CREATING USER, PLEASE TRY AGAIN *00'; this.settings.LOGIN_ERROR = ascii(27)+'AERROR LOGGING IN, PLEASE TRY AGAIN *00'; this.settings.CANCEL_MSG = ascii(27)+'BPRESS 2 TO CANCEL'; this.settings.SYS_ERROR = ascii(27)+'ASYS ERR, TRY AGAIN OR TELL US ON *08'; this.settings.LOADING = ascii(27)+'Cloading...'; this.settings.PROCESSING = ESC+VIEWDATA_YELLOW+'processing...'; var blp = 0; // Length of data on the bottom line /** * Set the attribute at the current position */ this.attr = function(field) { //NOOP - the terminal takes care of this } this.baselineClear = function(reposition) { msg = ''; log(LOG_DEBUG,'- Clear Bottom Line ['+blp+'] - reposition ['+reposition+']'); write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ ((blp > msg.length) ? (' '.repeat(blp-msg.length)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(msg.length) : '')) : '') ); blp = msg.length; } /** * Send a message to the baseline. * * @param text * @param reposition */ this.baselineSend = function(text,reposition) { var msg = this.getMessage(text); var x = this.strlen(msg); log(LOG_DEBUG,'- Bottom Line ['+msg+'] ('+x+') - reposition ['+reposition+'] BLP:'+blp); write_raw(VIEWDATA_HOME+VIEWDATA_UP+msg+ ((blp > x) ? (' '.repeat(blp-x)+(reposition ? VIEWDATA_HOME+VIEWDATA_UP+VIEWDATA_RIGHT.repeat(x) : '')) : '') ); blp = x; } /** * Turn off the cursor */ this.cursorOff = function() { write_raw(VIEWDATA_COFF); } /** * Turn on cursor * @param x * @param y */ this.cursorOn = function(x,y) { write_raw(VIEWDATA_CON); if (x && y) this.gotoxy(x,y); } // Field backspace, that leaves the field filler char this.fieldbs = function(char) { log(LOG_DEBUG,'- Field backspace with char:'+char); write_raw(VIEWDATA_LEFT+char+VIEWDATA_LEFT); } this.gotoxy = function(x,y) { log(LOG_DEBUG,'- Moving cursor to y:'+y+', x:'+x); // @todo This could be optimised to go the shortest route write_raw(VIEWDATA_HOME); if (x > 0) write_raw(VIEWDATA_RIGHT.repeat(x)); if (y > 0) write_raw(VIEWDATA_DOWN.repeat(y)); } this.strlen = function(str) { return str.replace(/\x1b/g,'').length; }; this.qrcode = function(qr) { // Render the body var qrcode = VIEWDATA_HOME+VIEWDATA_DOWN.repeat(5); var offset = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-1; for (var x = -1; x < qr.size; x=x+3) { var line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE; for (var y = -1; y < qr.size; y=y+2) { var char = 0; //TL char |= ((x===-1) || (y===-1) || ! qr.getModule(x,y)) ? (1<<0) : (0<<0); //TR char |= ((x===-1) || (y === qr.size-1) || ! qr.getModule(x,y+1)) ? (1<<1) : (0<<1); //ML char |= ((y===-1) || ! qr.getModule(x+1,y)) ? (1<<2) : (0<<2); //MR char |= ((y === qr.size-1) || ! qr.getModule(x+1,y+1)) ? (1<<3) : (0<<3); //BL char |= ((x===qr.size-2) || (y===-1) || ! qr.getModule(x+2,y)) ? (1<<4) : (0<<4); //BR char |= ((x===qr.size-2) || (y === qr.size-1) || ! qr.getModule(x+2,y+1)) ? (1<<5) : (0<<5); char += 0x20; if (char > 0x3f) char += 0x20; line += ascii(char); } // Render the right column if (y%2) line += '\x35'; repeat_count = this.settings.FRAME_WIDTH-Math.ceil(qr.size/2)-offset-(offset ? 1 : 2)-(y%2 === 1 ? 0 : 1); qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); // To fix some terminals where moving right from col 40 doesnt advance to col 1 on the next line qrcode +=VIEWDATA_LEFT+VIEWDATA_CR+VIEWDATA_DOWN; } log(LOG_DEBUG,'WIDTH:'+this.settings.FRAME_WIDTH); log(LOG_DEBUG,'QR :'+(Math.ceil(qr.size/2)+1)); log(LOG_DEBUG,'OFF :'+offset); log(LOG_DEBUG,'Y :'+(y%2 ? 0 : 1)); log(LOG_DEBUG,'X :'+(x%3 ? 0 : 1)); // Render the bottom if (x%3) { line = VIEWDATA_RIGHT.repeat(offset ? offset-1 : 0)+ESC+VIEWDATA_MOSIAC_WHITE; for (var y = 0; y < qr.size; y=y+2) { line += '\x23'; } // Render the right column if (y%2 === 0) { line += '\x21'; } qrcode += line+' '.repeat(repeat_count > 0 ? repeat_count : 0); } write_raw(qrcode); }; /* this.save=function() { file = system.mods_dir+'ansitex/text/'+this.page+'.tex'; w = new File(file); if (! w.open('w')) { log(LOG_ERROR,'! ERROR: Unable to create TEX file for '+this.page); exit(1); } w.write(JSON.stringify(this)); w.close(); log(LOG_DEBUG,'Saved file: '+this.page+'.tex'); } */ } function videotex(data) { var output = ''; //$output .= ($byte < 32) ? ESC.chr($byte+64) : chr($byte); for (var i = 0; i < data.length; i++) { output += (data.charCodeAt(i) < 32) ? "\x1b"+String.fromCharCode(data.charCodeAt(i)+64) : String.fromCharCode(data.charCodeAt(i)); } return output; } SessionProtocol.prototype = Session.prototype; SessionProtocol.prototype.constructor = SessionProtocol;