From 120e4198b9884609557fdbb902ef8762263269d9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 3 Apr 2012 16:14:04 -0500 Subject: [PATCH 001/131] make state machine more consistent. refactor. --- static/term.js | 265 +++++++++++++++++++++++-------------------------- 1 file changed, 124 insertions(+), 141 deletions(-) diff --git a/static/term.js b/static/term.js index 4f465a25..ae3c41ea 100644 --- a/static/term.js +++ b/static/term.js @@ -77,10 +77,10 @@ var Terminal = function(cols, rows, handler) { this.params = []; this.currentParam = 0; - var i = this.rows - 1; - this.lines = [ this.blankLine() ]; + this.lines = []; + var i = this.rows; while (i--) { - this.lines.push(this.lines[0].slice()); + this.lines.push(this.blankLine()); } }; @@ -675,23 +675,23 @@ Terminal.prototype.write = function(str) { } for (; i < l; i++) { - ch = str.charCodeAt(i); + ch = str[i]; switch (this.state) { case normal: switch (ch) { // '\0' - case 0: + case '\0': break; // '\a' - case 7: + case '\x07': this.bell(); break; // '\n', '\v', '\f' - case 10: - case 11: - case 12: + case '\n': + case '\x0b': + case '\x0c': if (this.convertEol) { this.x = 0; } @@ -705,19 +705,19 @@ Terminal.prototype.write = function(str) { break; // '\r' - case 13: + case '\r': this.x = 0; break; // '\b' - case 8: + case '\x08': if (this.x > 0) { this.x--; } break; // '\t' - case 9: + case '\t': // should check tabstops param = (this.x + 8) & ~7; if (param <= this.cols) { @@ -726,13 +726,13 @@ Terminal.prototype.write = function(str) { break; // '\e' - case 27: + case '\x1b': this.state = escaped; break; default: // ' ' - if (ch >= 32) { + if (ch >= ' ') { if (this.charset && this.charset[ch]) { ch = this.charset[ch]; } @@ -747,7 +747,8 @@ Terminal.prototype.write = function(str) { } } row = this.y + this.ybase; - this.lines[row][this.x] = (ch & 0xffff) | (this.curAttr << 16); + this.lines[row][this.x] = + (this.curAttr << 16) | (ch.charCodeAt(0) & 0xffff); this.x++; this.getRows(this.y); } @@ -755,7 +756,7 @@ Terminal.prototype.write = function(str) { } break; case escaped: - switch (str[i]) { + switch (ch) { // ESC [ Control Sequence Introducer ( CSI is 0x9b). case '[': this.params = []; @@ -871,13 +872,13 @@ Terminal.prototype.write = function(str) { default: this.state = normal; - console.log('Unknown ESC control: ' + str[i] + '.'); + console.log('Unknown ESC control: ' + ch + '.'); break; } break; case charset: - switch (str[i]) { + switch (ch) { // DEC Special Character and Line Drawing Set. case '0': this.charset = SCLD; @@ -892,88 +893,90 @@ Terminal.prototype.write = function(str) { break; case osc: - if (ch !== 27 && ch !== 7) break; + if (ch !== '\x1b' && ch !== '\x07') break; console.log('Unknown OSC code.'); this.state = normal; // increment for the trailing slash in ST - if (ch === 27) i++; + if (ch === '\x1b') i++; break; case csi: // '?', '>', '!' - if (ch === 63 || ch === 62 || ch === 33) { - this.prefix = str[i]; + if (ch === '?' || ch === '>' || ch === '!') { + this.prefix = ch; break; } // 0 - 9 - if (ch >= 48 && ch <= 57) { - this.currentParam = this.currentParam * 10 + ch - 48; + if (ch >= '0' && ch <= '9') { + this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; + // this.currentParam += ch; break; } // '$', '"', ' ', '\'' - if (ch === 36 || ch === 34 || ch === 32 || ch === 39) { - this.postfix = str[i]; + if (ch === '$' || ch === '"' || ch === ' ' || ch === '\'') { + this.postfix = ch; break; } this.params[this.params.length] = this.currentParam; + // this.params[this.params.length] = +this.currentParam; this.currentParam = 0; // ';' - if (ch === 59) break; + if (ch === ';') break; this.state = normal; switch (ch) { // CSI Ps A // Cursor Up Ps Times (default = 1) (CUU). - case 65: + case 'A': this.cursorUp(this.params); break; // CSI Ps B // Cursor Down Ps Times (default = 1) (CUD). - case 66: + case 'B': this.cursorDown(this.params); break; // CSI Ps C // Cursor Forward Ps Times (default = 1) (CUF). - case 67: + case 'C': this.cursorForward(this.params); break; // CSI Ps D // Cursor Backward Ps Times (default = 1) (CUB). - case 68: + case 'D': this.cursorBackward(this.params); break; // CSI Ps ; Ps H // Cursor Position [row;column] (default = [1,1]) (CUP). - case 72: + case 'H': this.cursorPos(this.params); break; // CSI Ps J Erase in Display (ED). - case 74: + case 'J': this.eraseInDisplay(this.params); break; // CSI Ps K Erase in Line (EL). - case 75: + case 'K': this.eraseInLine(this.params); break; // CSI Pm m Character Attributes (SGR). - case 109: + case 'm': this.charAttributes(this.params); break; // CSI Ps n Device Status Report (DSR). - case 110: + case 'n': this.deviceStatus(this.params); break; @@ -983,61 +986,61 @@ Terminal.prototype.write = function(str) { // CSI Ps @ // Insert Ps (Blank) Character(s) (default = 1) (ICH). - case 64: + case '@': this.insertChars(this.params); break; // CSI Ps E // Cursor Next Line Ps Times (default = 1) (CNL). - case 69: + case 'E': this.cursorNextLine(this.params); break; // CSI Ps F // Cursor Preceding Line Ps Times (default = 1) (CNL). - case 70: + case 'F': this.cursorPrecedingLine(this.params); break; // CSI Ps G // Cursor Character Absolute [column] (default = [row,1]) (CHA). - case 71: + case 'G': this.cursorCharAbsolute(this.params); break; // CSI Ps L // Insert Ps Line(s) (default = 1) (IL). - case 76: + case 'L': this.insertLines(this.params); break; // CSI Ps M // Delete Ps Line(s) (default = 1) (DL). - case 77: + case 'M': this.deleteLines(this.params); break; // CSI Ps P // Delete Ps Character(s) (default = 1) (DCH). - case 80: + case 'P': this.deleteChars(this.params); break; // CSI Ps X // Erase Ps Character(s) (default = 1) (ECH). - case 88: + case 'X': this.eraseChars(this.params); break; // CSI Pm ` Character Position Absolute // [column] (default = [row,1]) (HPA). - case 96: + case '`': this.charPosAbsolute(this.params); break; // 141 61 a * HPR - // Horizontal Position Relative - case 97: + case 'a': this.HPositionRelative(this.params); break; @@ -1045,37 +1048,37 @@ Terminal.prototype.write = function(str) { // Send Device Attributes (Primary DA). // CSI > P s c // Send Device Attributes (Secondary DA) - case 99: + case 'c': this.sendDeviceAttributes(this.params); break; // CSI Pm d // Line Position Absolute [row] (default = [1,column]) (VPA). - case 100: + case 'd': this.linePosAbsolute(this.params); break; // 145 65 e * VPR - Vertical Position Relative - case 101: + case 'e': this.VPositionRelative(this.params); break; // CSI Ps ; Ps f // Horizontal and Vertical Position [row;column] (default = // [1,1]) (HVP). - case 102: + case 'f': this.HVPosition(this.params); break; // CSI Pm h Set Mode (SM). // CSI ? Pm h - mouse escape codes, cursor escape codes - case 104: + case 'h': this.setMode(this.params); break; // CSI Pm l Reset Mode (RM). // CSI ? Pm l - case 108: + case 'l': this.resetMode(this.params); break; @@ -1083,17 +1086,17 @@ Terminal.prototype.write = function(str) { // Set Scrolling Region [top;bottom] (default = full size of win- // dow) (DECSTBM). // CSI ? Pm r - case 114: + case 'r': this.setScrollRegion(this.params); break; // CSI s Save cursor (ANSI.SYS). - case 115: + case 's': this.saveCursor(this.params); break; // CSI u Restore cursor (ANSI.SYS). - case 117: + case 'u': this.restoreCursor(this.params); break; @@ -1103,19 +1106,19 @@ Terminal.prototype.write = function(str) { // CSI Ps I // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). - case 73: + case 'I': this.cursorForwardTab(this.params); break; // CSI Ps S Scroll up Ps lines (default = 1) (SU). - case 83: + case 'S': this.scrollUp(this.params); break; // CSI Ps T Scroll down Ps lines (default = 1) (SD). // CSI Ps ; Ps ; Ps ; Ps ; Ps T // CSI > Ps; Ps T - case 84: + case 'T': // if (this.prefix === '>') { // this.resetTitleModes(this.params); // break; @@ -1129,29 +1132,29 @@ Terminal.prototype.write = function(str) { // CSI Ps Z // Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). - case 90: + case 'Z': this.cursorBackwardTab(this.params); break; // CSI Ps b Repeat the preceding graphic character Ps times (REP). - case 98: + case 'b': this.repeatPrecedingCharacter(this.params); break; // CSI Ps g Tab Clear (TBC). - // case 103: + // case 'g': // this.tabClear(this.params); // break; // CSI Pm i Media Copy (MC). // CSI ? Pm i - // case 105: + // case 'i': // this.mediaCopy(this.params); // break; // CSI Pm m Character Attributes (SGR). // CSI > Ps; Ps m - // case 109: // duplicate + // case 'm': // duplicate // if (this.prefix === '>') { // this.setResources(this.params); // } else { @@ -1161,7 +1164,7 @@ Terminal.prototype.write = function(str) { // CSI Ps n Device Status Report (DSR). // CSI > Ps n - // case 110: // duplicate + // case 'n': // duplicate // if (this.prefix === '>') { // this.disableModifiers(this.params); // } else { @@ -1176,7 +1179,7 @@ Terminal.prototype.write = function(str) { // CSI ? Ps$ p // Request DEC private mode (DECRQM). // CSI Ps ; Ps " p - case 112: + case 'p': switch (this.prefix) { // case '>': // this.setPointerMode(this.params); @@ -1202,7 +1205,7 @@ Terminal.prototype.write = function(str) { // CSI Ps q Load LEDs (DECLL). // CSI Ps SP q // CSI Ps " q - // case 113: + // case 'q': // if (this.postfix === ' ') { // this.setCursorStyle(this.params); // break; @@ -1219,7 +1222,7 @@ Terminal.prototype.write = function(str) { // dow) (DECSTBM). // CSI ? Pm r // CSI Pt; Pl; Pb; Pr; Ps$ r - // case 114: // duplicate + // case 'r': // duplicate // if (this.prefix === '?') { // this.restorePrivateValues(this.params); // } else if (this.postfix === '$') { @@ -1231,7 +1234,7 @@ Terminal.prototype.write = function(str) { // CSI s Save cursor (ANSI.SYS). // CSI ? Pm s - // case 115: // duplicate + // case 's': // duplicate // if (this.prefix === '?') { // this.savePrivateValues(this.params); // } else { @@ -1243,7 +1246,7 @@ Terminal.prototype.write = function(str) { // CSI Pt; Pl; Pb; Pr; Ps$ t // CSI > Ps; Ps t // CSI Ps SP t - // case 116: + // case 't': // if (this.postfix === '$') { // this.reverseAttrInRectangle(this.params); // } else if (this.postfix === ' ') { @@ -1259,7 +1262,7 @@ Terminal.prototype.write = function(str) { // CSI u Restore cursor (ANSI.SYS). // CSI Ps SP u - // case 117: // duplicate + // case 'u': // duplicate // if (this.postfix === ' ') { // this.setMarginBellVolume(this.params); // } else { @@ -1268,14 +1271,14 @@ Terminal.prototype.write = function(str) { // break; // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v - // case 118: + // case 'v': // if (this.postfix === '$') { // this.copyRectagle(this.params); // } // break; // CSI Pt ; Pl ; Pb ; Pr ' w - // case 119: + // case 'w': // if (this.postfix === '\'') { // this.enableFilterRectangle(this.params); // } @@ -1284,7 +1287,7 @@ Terminal.prototype.write = function(str) { // CSI Ps x Request Terminal Parameters (DECREQTPARM). // CSI Ps x Select Attribute Change Extent (DECSACE). // CSI Pc; Pt; Pl; Pb; Pr$ x - // case 120: + // case 'x': // if (this.postfix === '$') { // this.fillRectangle(this.params); // } else { @@ -1295,7 +1298,7 @@ Terminal.prototype.write = function(str) { // CSI Ps ; Pu ' z // CSI Pt; Pl; Pb; Pr$ z - // case 122: + // case 'z': // if (this.postfix === '\'') { // this.enableLocatorReporting(this.params); // } else if (this.postfix === '$') { @@ -1305,7 +1308,7 @@ Terminal.prototype.write = function(str) { // CSI Pm ' { // CSI Pt; Pl; Pb; Pr$ { - // case 123: + // case '{': // if (this.postfix === '\'') { // this.setLocatorEvents(this.params); // } else if (this.postfix === '$') { @@ -1314,7 +1317,7 @@ Terminal.prototype.write = function(str) { // break; // CSI Ps ' | - // case 124: + // case '|': // if (this.postfix === '\'') { // this.requestLocatorPosition(this.params); // } @@ -1322,7 +1325,7 @@ Terminal.prototype.write = function(str) { // CSI P m SP } // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. - // case 125: + // case '}': // if (this.postfix === ' ') { // this.insertColumns(this.params); // } @@ -1330,16 +1333,14 @@ Terminal.prototype.write = function(str) { // CSI P m SP ~ // Delete P s Column(s) (default = 1) (DECDC), VT420 and up - // case 126: + // case '~': // if (this.postfix === ' ') { // this.deleteColumns(this.params); // } // break; default: - console.log( - 'Unknown CSI code: %s', - str[i], this.params); + console.log('Unknown CSI code: %s', ch, this.params); break; } @@ -1710,10 +1711,8 @@ Terminal.prototype.eraseLine = function(x, y) { row = this.ybase + y; line = this.lines[row]; - // screen: - // ch = 32 | (this.defAttr << 16); // xterm, linux: - ch = 32 | (this.curAttr << 16); + ch = (this.curAttr << 16) | 32; for (i = x; i < this.cols; i++) { line[i] = ch; @@ -1727,7 +1726,7 @@ Terminal.prototype.blankLine = function(cur) { ? this.curAttr : this.defAttr; - var ch = 32 | (attr << 16) + var ch = (attr << 16) | 32 , line = [] , i = 0; @@ -1908,8 +1907,6 @@ Terminal.prototype.eraseInLine = function(params) { case 1: var x = this.x + 1; var line = this.lines[this.ybase + this.y]; - // screen: - //var ch = (this.defAttr << 16) | 32; // xterm, linux: var ch = (this.curAttr << 16) | 32; while (x--) line[x] = ch; @@ -1917,8 +1914,6 @@ Terminal.prototype.eraseInLine = function(params) { case 2: var x = this.cols; var line = this.lines[this.ybase + this.y]; - // screen: - //var ch = (this.defAttr << 16) | 32; // xterm, linux: var ch = (this.curAttr << 16) | 32; while (x--) line[x] = ch; @@ -2004,12 +1999,12 @@ Terminal.prototype.charAttributes = function(params) { this.curAttr = (this.curAttr & ~31) | (p - 40); } else if (p >= 90 && p <= 97) { // fg color 16 + p += 8; this.curAttr = (this.curAttr & ~(31 << 5)) | ((p - 90) << 5); - this.curAttr = this.curAttr | (8 << 5); } else if (p >= 100 && p <= 107) { // bg color 16 + p += 8; this.curAttr = (this.curAttr & ~31) | (p - 100); - this.curAttr = this.curAttr | 8; } else if (p === 0) { // default this.curAttr = this.defAttr; @@ -2050,20 +2045,14 @@ Terminal.prototype.charAttributes = function(params) { // fg color 256 if (params[i+1] !== 5) continue; i += 2; - this.highFg = params[i]; - // attempt to set - this.highFg = (this.highFg + 18) & 31; - this.curAttr = this.curAttr & ~(31 << 5); - this.curAttr = this.curAttr | (this.highFg << 5); + p = params[i]; + this.curAttr = (this.curAttr & ~(31 << 5)) | (p << 5); } else if (p === 48) { // bg color 256 if (params[i+1] !== 5) continue; i += 2; - this.highBg = params[i]; - // attempt to set - this.highBg = (this.highBg + 18) & 31; - this.curAttr = this.curAttr & ~31; - this.curAttr = this.curAttr | this.highBg; + p = params[i]; + this.curAttr = (this.curAttr & ~31) | p; } } } @@ -2147,8 +2136,6 @@ Terminal.prototype.insertChars = function(params) { row = this.y + this.ybase; j = this.x; while (param-- && j < this.cols) { - // screen: - //this.lines[row].splice(j++, 0, (this.defAttr << 16) | 32); // xterm, linux: this.lines[row].splice(j++, 0, (this.curAttr << 16) | 32); this.lines[row].pop(); @@ -2247,8 +2234,6 @@ Terminal.prototype.deleteChars = function(params) { row = this.y + this.ybase; while (param--) { this.lines[row].splice(this.x, 1); - // screen: - //this.lines[row].push((this.defAttr << 16) | 32); // xterm, linux: this.lines[row].push((this.curAttr << 16) | 32); } @@ -2263,8 +2248,6 @@ Terminal.prototype.eraseChars = function(params) { row = this.y + this.ybase; j = this.x; while (param-- && j < this.cols) { - // screen: - // this.lines[row][j++] = (this.defAttr << 16) | 32; // xterm, linux: this.lines[row][j++] = (this.curAttr << 16) | 32; } @@ -3261,7 +3244,7 @@ Terminal.prototype.insertColumns = function() { while (param--) { for (i = this.ybase; i < l; i++) { // xterm behavior uses curAttr? - this.lines[i].splice(this.x + 1, 0, (this.defAttr << 16) | 32); + this.lines[i].splice(this.x + 1, 0, (this.curAttr << 16) | 32); this.lines[i].pop(); } } @@ -3280,7 +3263,7 @@ Terminal.prototype.deleteColumns = function() { for (i = this.ybase; i < l; i++) { this.lines[i].splice(this.x, 1); // xterm behavior uses curAttr? - this.lines[i].push((this.defAttr << 16) | 32); + this.lines[i].push((this.curAttr << 16) | 32); } } }; @@ -3297,38 +3280,38 @@ Terminal.prototype.deleteColumns = function() { // reference above. The table below uses // the exact same charset xterm outputs. var SCLD = { - 95: 0x005f, // '_' - blank ? should this be ' ' ? - 96: 0x25c6, // '◆' - 97: 0x2592, // '▒' - 98: 0x0062, // 'b' - should this be: '\t' ? - 99: 0x0063, // 'c' - should this be: '\f' ? - 100: 0x0064, // 'd' - should this be: '\r' ? - 101: 0x0065, // 'e' - should this be: '\n' ? - 102: 0x00b0, // '°' - 103: 0x00b1, // '±' - 104: 0x2592, // '▒' - NL ? should this be '\n' ? - 105: 0x2603, // '☃' - VT ? should this be '\v' ? - 106: 0x2518, // '┘' - 107: 0x2510, // '┐' - 108: 0x250c, // '┌' - 109: 0x2514, // '└' - 110: 0x253c, // '┼' - 111: 0x23ba, // '⎺' - 112: 0x23bb, // '⎻' - 113: 0x2500, // '─' - 114: 0x23bc, // '⎼' - 115: 0x23bd, // '⎽' - 116: 0x251c, // '├' - 117: 0x2524, // '┤' - 118: 0x2534, // '┴' - 119: 0x252c, // '┬' - 120: 0x2502, // '│' - 121: 0x2264, // '≤' - 122: 0x2265, // '≥' - 123: 0x03c0, // 'π' - 124: 0x2260, // '≠' - 125: 0x00a3, // '£' - 126: 0x00b7 // '·' + '_': '\u005f', // '_' - blank ? should this be ' ' ? + '`': '\u25c6', // '◆' + 'a': '\u2592', // '▒' + 'b': '\u0062', // 'b' - should this be: '\t' ? + 'c': '\u0063', // 'c' - should this be: '\f' ? + 'd': '\u0064', // 'd' - should this be: '\r' ? + 'e': '\u0065', // 'e' - should this be: '\n' ? + 'f': '\u00b0', // '°' + 'g': '\u00b1', // '±' + 'h': '\u2592', // '▒' - NL ? should this be '\n' ? + 'i': '\u2603', // '☃' - VT ? should this be '\v' ? + 'j': '\u2518', // '┘' + 'k': '\u2510', // '┐' + 'l': '\u250c', // '┌' + 'm': '\u2514', // '└' + 'n': '\u253c', // '┼' + 'o': '\u23ba', // '⎺' + 'p': '\u23bb', // '⎻' + 'q': '\u2500', // '─' + 'r': '\u23bc', // '⎼' + 's': '\u23bd', // '⎽' + 't': '\u251c', // '├' + 'u': '\u2524', // '┤' + 'v': '\u2534', // '┴' + 'w': '\u252c', // '┬' + 'x': '\u2502', // '│' + 'y': '\u2264', // '≤' + 'z': '\u2265', // '≥' + '{': '\u03c0', // 'π' + '|': '\u2260', // '≠' + '}': '\u00a3', // '£' + '~': '\u00b7' // '·' }; /** From 14a07143a0b6bd5b7badd9dba8e5e1f8573f9b02 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 3 Apr 2012 17:49:53 -0500 Subject: [PATCH 002/131] set defaultColors differently --- lib/config.js | 8 +++++++- lib/tty.js | 3 --- static/term.js | 29 ++++++++++++++--------------- 3 files changed, 21 insertions(+), 19 deletions(-) diff --git a/lib/config.js b/lib/config.js index 942c3d21..53be6942 100644 --- a/lib/config.js +++ b/lib/config.js @@ -109,7 +109,13 @@ function readConfig(name) { conf.shellArgs = conf.shellArgs || []; // $TERM - conf.term.termName = conf.termName || conf.term.termName || 'xterm'; + if (!conf.term.termName) { + // tput -Txterm-256color longname + conf.term.termName = exists('/usr/share/terminfo/x/xterm+256color') + ? 'xterm-256color' + : 'xterm'; + } + conf.term.termName = conf.termName || conf.term.termName; conf.termName = conf.term.termName; // limits diff --git a/lib/tty.js b/lib/tty.js index 615cbda9..d7aa83da 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -94,9 +94,6 @@ function applyConfig() { for (; i < l; i++) { Terminal.colors[i] = Terminal.options.colors[i]; } - } else if (key === 'defaultColors') { - Terminal.colors[256] = Terminal.defaultColors[0]; - Terminal.colors[257] = Terminal.defaultColors[1]; } else { Terminal[key] = Terminal.options[key]; } diff --git a/static/term.js b/static/term.js index 859eed00..5297e142 100644 --- a/static/term.js +++ b/static/term.js @@ -149,6 +149,7 @@ Terminal.colors = function() { i = 0; for (; i < 256; i++) { c = colors[i]; + c[0] = c[0].toString(16); c[1] = c[1].toString(16); c[2] = c[2].toString(16); @@ -173,14 +174,14 @@ Terminal.colors = function() { return colors; }(); -Terminal.defaultColors = [ - // default bg/fg: - '#000000', - '#f0f0f0' -]; +// save fallback +Terminal._colors = Terminal.colors; -Terminal.colors[256] = Terminal.defaultColors[0]; -Terminal.colors[257] = Terminal.defaultColors[1]; +// default bg/fg +Terminal.defaultColors = { + bg: '#000000', + fg: '#f0f0f0' +}; Terminal.termName = ''; Terminal.geometry = [80, 30]; @@ -298,12 +299,8 @@ Terminal.prototype.open = function() { } // sync default bg/fg colors - this.element.style.backgroundColor = Terminal.colors[256]; - this.element.style.color = Terminal.colors[257]; - - // otherwise: - // Terminal.colors[256] = css(this.element, 'background-color'); - // Terminal.colors[257] = css(this.element, 'color'); + this.element.style.backgroundColor = Terminal.defaultColors.bg; + this.element.style.color = Terminal.defaultColors.fg; }; // XTerm mouse events @@ -597,13 +594,15 @@ Terminal.prototype.refresh = function(start, end) { if (bgColor !== 256) { out += 'background-color:' - + (Terminal.colors[bgColor] || 'pink') + + (Terminal.colors[bgColor] + || Terminal._colors[bgColor]) + ';'; } if (fgColor !== 257) { out += 'color:' - + (Terminal.colors[fgColor] || 'pink') + + (Terminal.colors[fgColor] + || Terminal._colors[fgColor]) + ';'; } From 779a8f3015ad1df9899cc27c0bda96bf86689854 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 3 Apr 2012 23:58:52 -0500 Subject: [PATCH 003/131] remove comments. readme. --- README.md | 6 ++++ static/term.js | 75 +++++++++++++++++++++++++++++++++----------------- 2 files changed, 55 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index 415baee5..969844c5 100644 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Bellard's vt100 for [jslinux](http://bellard.org/jslinux/). - Screen/Tmux-like keys (optional) - Ability to efficiently render programs: vim, mc, irssi, vifm, etc. - Support for xterm mouse events +- 256 color support ## Install @@ -87,6 +88,11 @@ JSON file. An example configuration file looks like: Usernames and passwords can be plaintext or sha1 hashes. +### 256 colors + +If tty.js fails to check your terminfo properly, you can force your `TERM` +to `xterm-256color` by setting `"termName": "xterm-256color"` in your config. + ### Example Hooks File ``` js diff --git a/static/term.js b/static/term.js index 5297e142..085709d4 100644 --- a/static/term.js +++ b/static/term.js @@ -515,11 +515,13 @@ Terminal.prototype.bindMouse = function() { */ // In the screen buffer, each character -// is stored as a 32-bit integer. -// First 16 bits: a utf-16 character. -// Next 5 bits: background color (0-31). -// Next 5 bits: foreground color (0-31). -// Next 6 bits: a mask for misc. flags: +// is stored as a an array with a character +// and a 32-bit integer. +// First value: a utf-16 character. +// Second value: +// Next 9 bits: background color (0-511). +// Next 9 bits: foreground color (0-511). +// Next 14 bits: a mask for misc. flags: // 1=bold, 2=underline, 4=inverse Terminal.prototype.refresh = function(start, end) { @@ -1156,12 +1158,14 @@ Terminal.prototype.write = function(str) { this.setScrollRegion(this.params); break; - // CSI s Save cursor (ANSI.SYS). + // CSI s + // Save cursor (ANSI.SYS). case 's': this.saveCursor(this.params); break; - // CSI u Restore cursor (ANSI.SYS). + // CSI u + // Restore cursor (ANSI.SYS). case 'u': this.restoreCursor(this.params); break; @@ -1825,12 +1829,10 @@ Terminal.prototype.reverseIndex = function() { this.y--; if (this.y < this.scrollTop) { this.y++; - // echo -ne '\e[1;1H\e[44m\eM\e[0m' - // use this.blankLine(false) for screen behavior + // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' + // blankLine(true) is xterm/linux behavior this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); j = this.rows - 1 - this.scrollBottom; - // add an extra one because we just added a line - // maybe put this above this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1); this.refreshStart = 0; this.refreshEnd = this.rows - 1; @@ -2252,18 +2254,15 @@ Terminal.prototype.insertLines = function(params) { row = this.y + this.ybase; j = this.rows - 1 - this.scrollBottom; - // add an extra one because we added one - // above j = this.rows - 1 + this.ybase - j + 1; while (param--) { - // this.blankLine(false) for screen behavior // test: echo -e '\e[44m\e[1L\e[0m' + // blankLine(true) - xterm/linux behavior this.lines.splice(row, 0, this.blankLine(true)); this.lines.splice(j, 1); } - //this.refresh(0, this.rows - 1); this.refreshStart = 0; this.refreshEnd = this.rows - 1; }; @@ -2280,13 +2279,12 @@ Terminal.prototype.deleteLines = function(params) { j = this.rows - 1 + this.ybase - j; while (param--) { - // this.blankLine(false) for screen behavior // test: echo -e '\e[44m\e[1M\e[0m' + // blankLine(true) - xterm/linux behavior this.lines.splice(j + 1, 0, this.blankLine(true)); this.lines.splice(row, 1); } - //this.refresh(0, this.rows - 1); this.refreshStart = 0; this.refreshEnd = this.rows - 1; }; @@ -2767,13 +2765,15 @@ Terminal.prototype.setScrollRegion = function(params) { this.y = 0; }; -// CSI s Save cursor (ANSI.SYS). +// CSI s +// Save cursor (ANSI.SYS). Terminal.prototype.saveCursor = function(params) { this.savedX = this.x; this.savedY = this.y; }; -// CSI u Restore cursor (ANSI.SYS). +// CSI u +// Restore cursor (ANSI.SYS). Terminal.prototype.restoreCursor = function(params) { this.x = this.savedX || 0; this.y = this.savedY || 0; @@ -2783,7 +2783,8 @@ Terminal.prototype.restoreCursor = function(params) { * Lesser Used */ -// CSI Ps I Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). +// CSI Ps I +// Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). Terminal.prototype.cursorForwardTab = function(params) { var row, param, line, ch; @@ -2807,10 +2808,7 @@ Terminal.prototype.cursorForwardTab = function(params) { Terminal.prototype.scrollUp = function(params) { var param = params[0] || 1; while (param--) { - //this.lines.shift(); - //this.lines.push(this.blankLine()); this.lines.splice(this.ybase + this.scrollTop, 1); - // no need to add 1 here, because we removed a line this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); } this.refreshStart = 0; @@ -2821,8 +2819,6 @@ Terminal.prototype.scrollUp = function(params) { Terminal.prototype.scrollDown = function(params) { var param = params[0] || 1; while (param--) { - //this.lines.pop(); - //this.lines.unshift(this.blankLine()); this.lines.splice(this.ybase + this.scrollBottom, 1); this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); } @@ -2850,6 +2846,7 @@ Terminal.prototype.initMouseTracking = function(params) { // Ps = 3 -> Do not query window/icon labels using UTF-8. // (See discussion of "Title Modes"). Terminal.prototype.resetTitleModes = function(params) { + ; }; // CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). @@ -2883,6 +2880,7 @@ Terminal.prototype.repeatPrecedingCharacter = function(params) { // Ps = 0 -> Clear Current Column (default). // Ps = 3 -> Clear All. Terminal.prototype.tabClear = function(params) { + ; }; // CSI Pm i Media Copy (MC). @@ -2897,6 +2895,7 @@ Terminal.prototype.tabClear = function(params) { // Ps = 1 0 -> Print composed display, ignores DECPEX. // Ps = 1 1 -> Print all pages. Terminal.prototype.mediaCopy = function(params) { + ; }; // CSI > Ps; Ps m @@ -2912,6 +2911,7 @@ Terminal.prototype.mediaCopy = function(params) { // If no parameters are given, all resources are reset to their // initial values. Terminal.prototype.setResources = function(params) { + ; }; // CSI > Ps n @@ -2928,6 +2928,7 @@ Terminal.prototype.setResources = function(params) { // adding a parameter to each function key to denote the modi- // fiers. Terminal.prototype.disableModifiers = function(params) { + ; }; // CSI > Ps p @@ -2939,6 +2940,7 @@ Terminal.prototype.disableModifiers = function(params) { // Ps = 2 -> always hide the pointer. If no parameter is // given, xterm uses the default, which is 1 . Terminal.prototype.setPointerMode = function(params) { + ; }; // CSI ! p Soft terminal reset (DECSTR). @@ -2957,6 +2959,7 @@ Terminal.prototype.softReset = function(params) { // 3 - permanently set // 4 - permanently reset Terminal.prototype.requestAnsiMode = function(params) { + ; }; // CSI ? Ps$ p @@ -2965,6 +2968,7 @@ Terminal.prototype.requestAnsiMode = function(params) { // where Ps is the mode number as in DECSET, Pm is the mode value // as in the ANSI DECRQM. Terminal.prototype.requestPrivateMode = function(params) { + ; }; // CSI Ps ; Ps " p @@ -2978,6 +2982,7 @@ Terminal.prototype.requestPrivateMode = function(params) { // Ps = 1 -> 7-bit controls (always set for VT100). // Ps = 2 -> 8-bit controls. Terminal.prototype.setConformanceLevel = function(params) { + ; }; // CSI Ps q Load LEDs (DECLL). @@ -2989,6 +2994,7 @@ Terminal.prototype.setConformanceLevel = function(params) { // Ps = 2 2 -> Extinguish Caps Lock. // Ps = 2 3 -> Extinguish Scroll Lock. Terminal.prototype.loadLEDs = function(params) { + ; }; // CSI Ps SP q @@ -2999,6 +3005,7 @@ Terminal.prototype.loadLEDs = function(params) { // Ps = 3 -> blinking underline. // Ps = 4 -> steady underline. Terminal.prototype.setCursorStyle = function(params) { + ; }; // CSI Ps " q @@ -3008,12 +3015,14 @@ Terminal.prototype.setCursorStyle = function(params) { // Ps = 1 -> DECSED and DECSEL cannot erase. // Ps = 2 -> DECSED and DECSEL can erase. Terminal.prototype.setCharProtectionAttr = function(params) { + ; }; // CSI ? Pm r // Restore DEC Private Mode Values. The value of Ps previously // saved is restored. Ps values are the same as for DECSET. Terminal.prototype.restorePrivateValues = function(params) { + ; }; // CSI Pt; Pl; Pb; Pr; Ps$ r @@ -3043,6 +3052,7 @@ Terminal.prototype.setAttrInRectangle = function(params) { // Save DEC Private Mode Values. Ps values are the same as for // DECSET. Terminal.prototype.savePrivateValues = function(params) { + ; }; // CSI Ps ; Ps ; Ps t @@ -3092,6 +3102,7 @@ Terminal.prototype.savePrivateValues = function(params) { // Ps = 2 3 ; 2 -> Restore xterm window title from stack. // Ps >= 2 4 -> Resize to Ps lines (DECSLPP). Terminal.prototype.manipulateWindow = function(params) { + ; }; // CSI Pt; Pl; Pb; Pr; Ps$ t @@ -3101,6 +3112,7 @@ Terminal.prototype.manipulateWindow = function(params) { // Ps denotes the attributes to reverse, i.e., 1, 4, 5, 7. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.reverseAttrInRectangle = function(params) { + ; }; // CSI > Ps; Ps t @@ -3112,6 +3124,7 @@ Terminal.prototype.reverseAttrInRectangle = function(params) { // Ps = 3 -> Query window/icon labels using UTF-8. (See dis- // cussion of "Title Modes") Terminal.prototype.setTitleModeFeature = function(params) { + ; }; // CSI Ps SP t @@ -3120,6 +3133,7 @@ Terminal.prototype.setTitleModeFeature = function(params) { // Ps = 2 , 3 or 4 -> low. // Ps = 5 , 6 , 7 , or 8 -> high. Terminal.prototype.setWarningBellVolume = function(params) { + ; }; // CSI Ps SP u @@ -3128,6 +3142,7 @@ Terminal.prototype.setWarningBellVolume = function(params) { // Ps = 2 , 3 or 4 -> low. // Ps = 0 , 5 , 6 , 7 , or 8 -> high. Terminal.prototype.setMarginBellVolume = function(params) { + ; }; // CSI Pt; Pl; Pb; Pr; Pp; Pt; Pl; Pp$ v @@ -3138,6 +3153,7 @@ Terminal.prototype.setMarginBellVolume = function(params) { // Pp denotes the target page. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.copyRectangle = function(params) { + ; }; // CSI Pt ; Pl ; Pb ; Pr ' w @@ -3152,6 +3168,7 @@ Terminal.prototype.copyRectangle = function(params) { // ted, any locator motion will be reported. DECELR always can- // cels any prevous rectangle definition. Terminal.prototype.enableFilterRectangle = function(params) { + ; }; // CSI Ps x Request Terminal Parameters (DECREQTPARM). @@ -3166,13 +3183,15 @@ Terminal.prototype.enableFilterRectangle = function(params) { // Pn = 1 <- clock multiplier. // Pn = 0 <- STP flags. Terminal.prototype.requestParameters = function(params) { + ; }; // CSI Ps x Select Attribute Change Extent (DECSACE). // Ps = 0 -> from start to end position, wrapped. // Ps = 1 -> from start to end position, wrapped. // Ps = 2 -> rectangle (exact). -Terminal.prototype.__ = function(params) { +Terminal.prototype.selectChangeExtent = function(params) { + ; }; // CSI Pc; Pt; Pl; Pb; Pr$ x @@ -3211,6 +3230,7 @@ Terminal.prototype.fillRectangle = function(params) { // Pu = 1 <- device physical pixels. // Pu = 2 <- character cells. Terminal.prototype.enableLocatorReporting = function(params) { + ; }; // CSI Pt; Pl; Pb; Pr$ z @@ -3247,12 +3267,14 @@ Terminal.prototype.eraseRectangle = function(params) { // Ps = 3 -> report button up transitions. // Ps = 4 -> do not report button up transitions. Terminal.prototype.setLocatorEvents = function(params) { + ; }; // CSI Pt; Pl; Pb; Pr$ { // Selective Erase Rectangular Area (DECSERA), VT400 and up. // Pt; Pl; Pb; Pr denotes the rectangle. Terminal.prototype.selectiveEraseRectangle = function(params) { + ; }; // CSI Ps ' | @@ -3296,6 +3318,7 @@ Terminal.prototype.selectiveEraseRectangle = function(params) { // The ``page'' parameter is not used by xterm, and will be omit- // ted. Terminal.prototype.requestLocatorPosition = function(params) { + ; }; // CSI P m SP } From da5103f25eab7efaa55cd65bcbe95e93788dc72f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 00:01:35 -0500 Subject: [PATCH 004/131] always show cursor after switching screen buffers. --- static/term.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/term.js b/static/term.js index 085709d4..3afcb04e 100644 --- a/static/term.js +++ b/static/term.js @@ -2604,6 +2604,7 @@ Terminal.prototype.setMode = function(params) { }; this.reset(); this.normal = normal; + this.showCursor(); } break; } @@ -2747,6 +2748,7 @@ Terminal.prototype.resetMode = function(params) { // this.y = this.savedY; // } this.refresh(0, this.rows - 1); + this.showCursor(); } break; } From 12021ce45b8471152a93a94e96871d3ff154a05a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 00:08:28 -0500 Subject: [PATCH 005/131] hide h1 and #help on lights out --- static/style.css | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/style.css b/static/style.css index 6b231b14..86b570e3 100644 --- a/static/style.css +++ b/static/style.css @@ -179,3 +179,5 @@ h1 { bottom: 10px; left: 10px; } + +.dark h1, .dark #help { display: none; } From dcd1ad2d1caec82bc50697b3741174ab468b5b35 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 00:40:04 -0500 Subject: [PATCH 006/131] handle OSC title code --- static/term.js | 93 +++++++++++++++++++++++++++++++++++++++++++++----- static/tty.js | 62 ++++++++++++++++++++++++++------- 2 files changed, 135 insertions(+), 20 deletions(-) diff --git a/static/term.js b/static/term.js index 3afcb04e..b14e75e2 100644 --- a/static/term.js +++ b/static/term.js @@ -47,7 +47,8 @@ var normal = 0 var Terminal = function(cols, rows, handler) { this.cols = cols; this.rows = rows; - this.handler = handler; + if (handler) this.handler = handler; + this.ybase = 0; this.ydisp = 0; this.x = 0; @@ -961,11 +962,84 @@ Terminal.prototype.write = function(str) { break; case osc: - if (ch !== '\x1b' && ch !== '\x07') break; - console.log('Unknown OSC code.'); - this.state = normal; - // increment for the trailing slash in ST - if (ch === '\x1b') i++; + // OSC Ps ; Pt ST + // OSC Ps ; Pt BEL + // Set Text Parameters. + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; + + this.params.push(this.currentParam); + + switch (this.params[0]) { + case 0: + case 1: + case 2: + if (this.params[1]) { + this.handleTitle(this.params[1]); + } + break; + case 3: + // set X property + break; + case 4: + case 5: + // change dynamic colors + break; + case 10: + case 11: + case 12: + case 13: + case 14: + case 15: + case 16: + case 17: + case 18: + case 19: + // change dynamic ui colors + break; + case 46: + // change log file + break; + case 50: + // dynamic font + break; + case 51: + // emacs shell + break; + case 52: + // manipulate selection data + break; + case 104: + case 105: + case 110: + case 111: + case 112: + case 113: + case 114: + case 115: + case 116: + case 117: + case 118: + // reset colors + break; + } + + this.params = []; + this.currentParam = 0; + this.state = normal; + } else { + if (!this.params.length) { + if (ch >= '0' && ch <= '9') { + this.currentParam = + this.currentParam * 10 + ch.charCodeAt(0) - 48; + } else if (ch === ';') { + this.params.push(this.currentParam); + this.currentParam = ''; + } + } else { + this.currentParam += ch; + } + } break; case csi: @@ -988,8 +1062,8 @@ Terminal.prototype.write = function(str) { break; } - this.params[this.params.length] = this.currentParam; - // this.params[this.params.length] = +this.currentParam; + this.params.push(this.currentParam); + // this.params.push(+this.currentParam); this.currentParam = 0; // ';' @@ -1807,6 +1881,9 @@ Terminal.prototype.blankLine = function(cur) { return line; }; +Terminal.prototype.handler = function() {}; +Terminal.prototype.handleTitle = function() {}; + /** * ESC */ diff --git a/static/tty.js b/static/tty.js index 7c1b92d3..1860359c 100644 --- a/static/tty.js +++ b/static/tty.js @@ -12,7 +12,9 @@ var doc = this.document , win = this , root - , body; + , body + , h1 + , initialTitle = doc.title; /** * Shared @@ -32,6 +34,7 @@ function open() { root = doc.documentElement; body = doc.body; + h1 = doc.getElementsByTagName('h1')[0]; socket = io.connect(); windows = []; @@ -435,9 +438,8 @@ function Tab(win) { , cols = win.cols , rows = win.rows; - Terminal.call(this, cols, rows, function(data) { - socket.emit('data', self.id, data); - }); + // TODO: make this an EventEmitter + Terminal.call(this, cols, rows); var button = document.createElement('div'); button.className = 'tab'; @@ -466,13 +468,29 @@ function Tab(win) { socket.emit('create', cols, rows, function(err, data) { if (err) return self._destroy(); self.pty = data.pty; - self.process = data.process; - win.title.innerHTML = data.process; + self.setProcessName(data.process); }); }; inherits(Tab, Terminal); +Tab.prototype.handler = function(data) { + socket.emit('data', this.id, data); +}; + +Tab.prototype.handleTitle = function(title) { + if (!title) return; + + title = sanitize(title); + document.title = title; + this.window.bar.title = title; + + this.title = title; + // this.setProcessName(this.process); + + // if (h1) h1.innerHTML = title; +}; + Tab.prototype._write = Tab.prototype.write; Tab.prototype.write = function(data) { @@ -504,6 +522,8 @@ Tab.prototype.focus = function() { this.button.style.color = ''; } + this.handleTitle(this.title); + this._focus(); win.focus(); @@ -537,6 +557,11 @@ Tab.prototype._destroy = function() { if (!win.tabs.length) { win.destroy(); } + + if (!windows.length) { + document.title = initialTitle; + if (h1) h1.innerHTML = initialTitle; + } }; Tab.prototype.destroy = function() { @@ -630,15 +655,23 @@ Tab.prototype.specialKeyHandler = function(ev) { Tab.prototype.pollProcessName = function(func) { var self = this; socket.emit('process', this.id, function(err, name) { - self.process = name; - self.button.title = name; - if (self.window.focused === self) { - self.window.title.innerHTML = name; - } - if (func) func(name); + if (!err) self.setProcessName(name); + if (func) func(err, name); }); }; +Tab.prototype.setProcessName = function(name) { + name = sanitize(name); + this.process = name; + this.button.title = name; + if (this.window.focused === this) { + // if (this.title) { + // name += ' (' + this.title + ')'; + // } + this.window.title.innerHTML = name; + } +}; + /** * Helpers */ @@ -682,6 +715,11 @@ function cancel(ev) { var isMac = ~navigator.userAgent.indexOf('Mac'); +function sanitize(text) { + if (!text) return ''; + return (text + '').replace(/[&<>]/g, '') +} + /** * Load */ From f2061091f19299e5be49fca2019d4ff2ed747c95 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 03:31:03 -0500 Subject: [PATCH 007/131] refactor eraseLine. refactor getRows. optimize. --- static/term.js | 96 ++++++++++++++++++++++++++------------------------ 1 file changed, 49 insertions(+), 47 deletions(-) diff --git a/static/term.js b/static/term.js index b14e75e2..cf463e3d 100644 --- a/static/term.js +++ b/static/term.js @@ -541,14 +541,15 @@ Terminal.prototype.refresh = function(start, end) { , row , parent; - width = this.cols; - - if (end - start === this.rows - 1) { + if (end - start >= this.rows / 2) { parent = this.element.parentNode; if (parent) parent.removeChild(this.element); } - for (y = start; y <= end; y++) { + width = this.cols; + y = start; + + for (; y <= end; y++) { row = y + this.ydisp; line = this.lines[row]; @@ -696,13 +697,16 @@ Terminal.prototype.scroll = function() { // subtract the bottom scroll region row -= this.rows - 1 - this.scrollBottom; - // potential optimization - // if (row === this.lines.length) { - // this.lines.push(this.blankLine()); - // } else - - // add our new line - this.lines.splice(row, 0, this.blankLine()); + if (row === this.lines.length) { + // potential optimization: + // pushing is faster than splicing + // when they amount to the same + // behavior. + this.lines.push(this.blankLine()); + } else { + // add our new line + this.lines.splice(row, 0, this.blankLine()); + } if (this.scrollTop !== 0) { if (this.ybase !== 0) { @@ -726,17 +730,14 @@ Terminal.prototype.scrollDisp = function(disp) { }; Terminal.prototype.write = function(str) { - // console.log(JSON.stringify(str.replace(/\x1b/g, '^['))); - var l = str.length , i = 0 , ch , param , row; - this.refreshStart = this.rows; - this.refreshEnd = -1; - this.getRows(this.y); + this.refreshStart = this.y; + this.refreshEnd = this.y; if (this.ybase !== this.ydisp) { this.ydisp = this.ybase; @@ -744,6 +745,8 @@ Terminal.prototype.write = function(str) { this.refreshEnd = this.rows - 1; } + // console.log(JSON.stringify(str.replace(/\x1b/g, '^['))); + for (; i < l; i++) { ch = str[i]; switch (this.state) { @@ -1490,6 +1493,8 @@ Terminal.prototype.write = function(str) { this.prefix = ''; this.postfix = ''; + + this.getRows(this.y); break; } } @@ -1832,8 +1837,6 @@ Terminal.prototype.resize = function(x, y) { this.scrollTop = 0; this.scrollBottom = y - 1; - this.refreshStart = 0; - this.refreshEnd = y - 1; this.refresh(0, this.rows - 1); @@ -1849,20 +1852,25 @@ Terminal.prototype.getRows = function(y) { this.refreshEnd = Math.max(this.refreshEnd, y); }; -Terminal.prototype.eraseLine = function(x, y) { - var line, i, ch, row; +Terminal.prototype.eraseRight = function(x, y) { + var line = this.lines[this.ybase + y] + , ch = [this.curAttr, ' ']; - row = this.ybase + y; + for (; x < this.cols; x++) { + line[x] = ch; + } +}; - line = this.lines[row]; - // xterm, linux: - ch = [this.curAttr, ' ']; +Terminal.prototype.eraseLeft = function(x, y) { + var line = this.lines[this.ybase + y] + , ch = [this.curAttr, ' ']; - for (i = x; i < this.cols; i++) { - line[i] = ch; - } + x++; + while (x--) line[x] = ch; +}; - this.getRows(y); +Terminal.prototype.eraseLine = function(y) { + this.eraseRight(0, y); }; Terminal.prototype.blankLine = function(cur) { @@ -2010,24 +2018,25 @@ Terminal.prototype.cursorPos = function(params) { // Ps = 1 -> Selective Erase Above. // Ps = 2 -> Selective Erase All. Terminal.prototype.eraseInDisplay = function(params) { - var param, row, j; + var j; switch (params[0] || 0) { case 0: - this.eraseLine(this.x, this.y); - for (j = this.y + 1; j < this.rows; j++) { - this.eraseLine(0, j); + this.eraseRight(this.x, this.y); + j = this.y + 1; + for (; j < this.rows; j++) { + this.eraseLine(j); } break; case 1: - this.eraseInLine([1]); + this.eraseLeft(this.x, this.y); j = this.y; while (j--) { - this.eraseLine(0, j); + this.eraseLine(j); } break; case 2: - this.eraseInDisplay([0]); - this.eraseInDisplay([1]); + j = this.rows; + while (j--) this.eraseLine(j); break; case 3: ; // no saved lines @@ -2047,21 +2056,13 @@ Terminal.prototype.eraseInDisplay = function(params) { Terminal.prototype.eraseInLine = function(params) { switch (params[0] || 0) { case 0: - this.eraseLine(this.x, this.y); + this.eraseRight(this.x, this.y); break; case 1: - var x = this.x + 1; - var line = this.lines[this.ybase + this.y]; - // xterm, linux: - var ch = [this.curAttr, ' ']; - while (x--) line[x] = ch; + this.eraseLeft(this.x, this.y); break; case 2: - var x = this.cols; - var line = this.lines[this.ybase + this.y]; - // xterm, linux: - var ch = [this.curAttr, ' ']; - while (x--) line[x] = ch; + this.eraseLine(this.y); break; } }; @@ -3518,6 +3519,7 @@ function isBoldBroken() { } var String = this.String; +var Math = this.Math; /** * Expose From c737d73b00d4c28dc6d96c1277edbb091eded960 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 04:27:34 -0500 Subject: [PATCH 008/131] refactor getRows. ignore DCS, APC, PM. fix broken bold. --- static/term.js | 59 +++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 29 deletions(-) diff --git a/static/term.js b/static/term.js index cf463e3d..c1d3fc57 100644 --- a/static/term.js +++ b/static/term.js @@ -587,7 +587,9 @@ Terminal.prototype.refresh = function(start, end) { flags = data >> 18; if (flags & 1) { - out += 'font-weight:bold;'; + if (!Terminal.brokenBold) { + out += 'font-weight:bold;'; + } // see: XTerm*boldColors if (fgColor < 8) fgColor += 8; } @@ -616,9 +618,6 @@ Terminal.prototype.refresh = function(start, end) { } switch (ch) { - case ' ': - out += ' '; - break; case '&': out += '&'; break; @@ -629,7 +628,7 @@ Terminal.prototype.refresh = function(start, end) { out += '>'; break; default: - if (ch < ' ') { + if (ch <= ' ') { out += ' '; } else { out += ch; @@ -741,8 +740,7 @@ Terminal.prototype.write = function(str) { if (this.ybase !== this.ydisp) { this.ydisp = this.ybase; - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); } // console.log(JSON.stringify(str.replace(/\x1b/g, '^['))); @@ -772,8 +770,7 @@ Terminal.prototype.write = function(str) { if (this.y >= this.scrollBottom + 1) { this.y--; this.scroll(); - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); } break; @@ -815,14 +812,13 @@ Terminal.prototype.write = function(str) { if (this.y >= this.scrollBottom + 1) { this.y--; this.scroll(); - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); } } row = this.y + this.ybase; this.lines[row][this.x] = [this.curAttr, ch]; this.x++; - this.getRows(this.y); + this.updateRange(); } break; } @@ -845,16 +841,22 @@ Terminal.prototype.write = function(str) { // ESC P Device Control String ( DCS is 0x90). case 'P': + this.params = [-1]; + this.currentParam = 0; this.state = osc; break; // ESC _ Application Program Command ( APC is 0x9f). case '_': + this.params = [-1]; + this.currentParam = 0; this.state = osc; break; // ESC ^ Privacy Message ( PM is 0x9e). case '^': + this.params = [-1]; + this.currentParam = 0; this.state = osc; break; @@ -1494,12 +1496,12 @@ Terminal.prototype.write = function(str) { this.prefix = ''; this.postfix = ''; - this.getRows(this.y); + this.updateRange(); break; } } - this.getRows(this.y); + this.updateRange(); if (this.refreshEnd >= this.refreshStart) { this.refresh(this.refreshStart, this.refreshEnd); @@ -1847,9 +1849,14 @@ Terminal.prototype.resize = function(x, y) { this.normal = null; }; -Terminal.prototype.getRows = function(y) { - this.refreshStart = Math.min(this.refreshStart, y); - this.refreshEnd = Math.max(this.refreshEnd, y); +Terminal.prototype.updateRange = function() { + this.refreshStart = Math.min(this.refreshStart, this.y); + this.refreshEnd = Math.max(this.refreshEnd, this.y); +}; + +Terminal.prototype.maxRange = function() { + this.refreshStart = 0; + this.refreshEnd = this.rows - 1; }; Terminal.prototype.eraseRight = function(x, y) { @@ -1902,8 +1909,7 @@ Terminal.prototype.index = function() { if (this.y >= this.scrollBottom + 1) { this.y--; this.scroll(); - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); } this.state = normal; }; @@ -1919,8 +1925,7 @@ Terminal.prototype.reverseIndex = function() { this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); j = this.rows - 1 - this.scrollBottom; this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1); - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); } this.state = normal; }; @@ -2341,8 +2346,7 @@ Terminal.prototype.insertLines = function(params) { this.lines.splice(j, 1); } - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); }; // CSI Ps M @@ -2363,8 +2367,7 @@ Terminal.prototype.deleteLines = function(params) { this.lines.splice(row, 1); } - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); }; // CSI Ps P @@ -2891,8 +2894,7 @@ Terminal.prototype.scrollUp = function(params) { this.lines.splice(this.ybase + this.scrollTop, 1); this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); } - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); }; // CSI Ps T Scroll down Ps lines (default = 1) (SD). @@ -2902,8 +2904,7 @@ Terminal.prototype.scrollDown = function(params) { this.lines.splice(this.ybase + this.scrollBottom, 1); this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); } - this.refreshStart = 0; - this.refreshEnd = this.rows - 1; + this.maxRange(); }; // CSI Ps ; Ps ; Ps ; Ps ; Ps T From 705b01723f36f6fad2e7a9e1fe379e68e55afc2a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 05:00:16 -0500 Subject: [PATCH 009/131] refactor csi codes. remove unused variables. --- static/term.js | 215 +++++++++++++++++++++++++++---------------------- 1 file changed, 117 insertions(+), 98 deletions(-) diff --git a/static/term.js b/static/term.js index c1d3fc57..5208cae2 100644 --- a/static/term.js +++ b/static/term.js @@ -932,21 +932,21 @@ Terminal.prototype.write = function(str) { // ESC = Application Keypad (DECPAM). case '=': - console.log('Serial port requested application keypad.'); + this.log('Serial port requested application keypad.'); this.applicationKeypad = true; this.state = normal; break; // ESC > Normal Keypad (DECPNM). case '>': - console.log('Switching back to normal keypad.'); + this.log('Switching back to normal keypad.'); this.applicationKeypad = false; this.state = normal; break; default: this.state = normal; - console.log('Unknown ESC control: ' + ch + '.'); + this.error('Unknown ESC control: ' + ch + '.'); break; } break; @@ -1489,7 +1489,7 @@ Terminal.prototype.write = function(str) { // break; default: - console.log('Unknown CSI code: %s', ch, this.params); + this.error('Unknown CSI code: %s', ch, this.params); break; } @@ -1777,6 +1777,18 @@ Terminal.prototype.bell = function() { if (Terminal.popOnBell) this.focus(); }; +Terminal.prototype.log = function(data) { + if (!Terminal.debug) return; + if (!window.console || !window.console.log) return; + window.console.log(data); +}; + +Terminal.prototype.error = function(data) { + if (!Terminal.debug) return; + if (!window.console || !window.console.error) return; + window.console.error(data); +}; + Terminal.prototype.resize = function(x, y) { var line , el @@ -1942,8 +1954,7 @@ Terminal.prototype.reset = function() { // CSI Ps A // Cursor Up Ps Times (default = 1) (CUU). Terminal.prototype.cursorUp = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.y -= param; if (this.y < 0) this.y = 0; @@ -1952,8 +1963,7 @@ Terminal.prototype.cursorUp = function(params) { // CSI Ps B // Cursor Down Ps Times (default = 1) (CUD). Terminal.prototype.cursorDown = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.y += param; if (this.y >= this.rows) { @@ -1964,8 +1974,7 @@ Terminal.prototype.cursorDown = function(params) { // CSI Ps C // Cursor Forward Ps Times (default = 1) (CUF). Terminal.prototype.cursorForward = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.x += param; if (this.x >= this.cols - 1) { @@ -1976,8 +1985,7 @@ Terminal.prototype.cursorForward = function(params) { // CSI Ps D // Cursor Backward Ps Times (default = 1) (CUB). Terminal.prototype.cursorBackward = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.x -= param; if (this.x < 0) this.x = 0; @@ -2135,76 +2143,83 @@ Terminal.prototype.eraseInLine = function(params) { // Ps = 4 8 ; 5 ; Ps -> Set background color to the second // Ps. Terminal.prototype.charAttributes = function(params) { - var i, p; + var i, l, p, bg, fg; + if (params.length === 0) { // default this.curAttr = this.defAttr; - } else { - for (i = 0; i < params.length; i++) { - p = params[i]; - if (p >= 30 && p <= 37) { - // fg color 8 - this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 30) << 9); - } else if (p >= 40 && p <= 47) { - // bg color 8 - this.curAttr = (this.curAttr & ~0x1ff) | (p - 40); - } else if (p >= 90 && p <= 97) { - // fg color 16 - p += 8; - this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 90) << 9); - } else if (p >= 100 && p <= 107) { - // bg color 16 - p += 8; - this.curAttr = (this.curAttr & ~0x1ff) | (p - 100); - } else if (p === 0) { - // default - this.curAttr = this.defAttr; - } else if (p === 1) { - // bold text - this.curAttr = this.curAttr | (1 << 18); - } else if (p === 4) { - // underlined text - this.curAttr = this.curAttr | (2 << 18); - } else if (p === 7 || p === 27) { - // inverse and positive - // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' - if (p === 7) { - if ((this.curAttr >> 18) & 4) continue; - this.curAttr = this.curAttr | (4 << 18); - } else if (p === 27) { - if (~(this.curAttr >> 18) & 4) continue; - this.curAttr = this.curAttr & ~(4 << 18); - } - var bg = this.curAttr & 0x1ff; - var fg = (this.curAttr >> 9) & 0x1ff; - this.curAttr = (this.curAttr & ~0x3ffff) | ((bg << 9) | fg); - } else if (p === 22) { - // not bold - this.curAttr = this.curAttr & ~(1 << 18); - } else if (p === 24) { - // not underlined - this.curAttr = this.curAttr & ~(2 << 18); - } else if (p === 39) { - // reset fg - this.curAttr = this.curAttr & ~(0x1ff << 9); - this.curAttr = this.curAttr | (((this.defAttr >> 9) & 0x1ff) << 9); - } else if (p === 49) { - // reset bg - this.curAttr = this.curAttr & ~0x1ff; - this.curAttr = this.curAttr | (this.defAttr & 0x1ff); - } else if (p === 38) { - // fg color 256 - if (params[i+1] !== 5) continue; - i += 2; - p = params[i]; - this.curAttr = (this.curAttr & ~(0x1ff << 9)) | (p << 9); - } else if (p === 48) { - // bg color 256 - if (params[i+1] !== 5) continue; - i += 2; - p = params[i]; - this.curAttr = (this.curAttr & ~0x1ff) | p; + return; + } + + l = params.length; + i = 0; + + for (; i < l; i++) { + p = params[i]; + if (p >= 30 && p <= 37) { + // fg color 8 + this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 30) << 9); + } else if (p >= 40 && p <= 47) { + // bg color 8 + this.curAttr = (this.curAttr & ~0x1ff) | (p - 40); + } else if (p >= 90 && p <= 97) { + // fg color 16 + p += 8; + this.curAttr = (this.curAttr & ~(0x1ff << 9)) | ((p - 90) << 9); + } else if (p >= 100 && p <= 107) { + // bg color 16 + p += 8; + this.curAttr = (this.curAttr & ~0x1ff) | (p - 100); + } else if (p === 0) { + // default + this.curAttr = this.defAttr; + } else if (p === 1) { + // bold text + this.curAttr = this.curAttr | (1 << 18); + } else if (p === 4) { + // underlined text + this.curAttr = this.curAttr | (2 << 18); + } else if (p === 7 || p === 27) { + // inverse and positive + // test with: echo -e '\e[31m\e[42mhello\e[7mworld\e[27mhi\e[m' + if (p === 7) { + if ((this.curAttr >> 18) & 4) continue; + this.curAttr = this.curAttr | (4 << 18); + } else if (p === 27) { + if (~(this.curAttr >> 18) & 4) continue; + this.curAttr = this.curAttr & ~(4 << 18); } + + bg = this.curAttr & 0x1ff; + fg = (this.curAttr >> 9) & 0x1ff; + + this.curAttr = (this.curAttr & ~0x3ffff) | ((bg << 9) | fg); + } else if (p === 22) { + // not bold + this.curAttr = this.curAttr & ~(1 << 18); + } else if (p === 24) { + // not underlined + this.curAttr = this.curAttr & ~(2 << 18); + } else if (p === 39) { + // reset fg + this.curAttr = this.curAttr & ~(0x1ff << 9); + this.curAttr = this.curAttr | (((this.defAttr >> 9) & 0x1ff) << 9); + } else if (p === 49) { + // reset bg + this.curAttr = this.curAttr & ~0x1ff; + this.curAttr = this.curAttr | (this.defAttr & 0x1ff); + } else if (p === 38) { + // fg color 256 + if (params[i+1] !== 5) continue; + i += 2; + p = params[i]; + this.curAttr = (this.curAttr & ~(0x1ff << 9)) | (p << 9); + } else if (p === 48) { + // bg color 256 + if (params[i+1] !== 5) continue; + i += 2; + p = params[i]; + this.curAttr = (this.curAttr & ~0x1ff) | p; } } }; @@ -2282,10 +2297,13 @@ Terminal.prototype.deviceStatus = function(params) { // Insert Ps (Blank) Character(s) (default = 1) (ICH). Terminal.prototype.insertChars = function(params) { var param, row, j; + param = params[0]; if (param < 1) param = 1; + row = this.y + this.ybase; j = this.x; + while (param-- && j < this.cols) { // xterm, linux: this.lines[row].splice(j++, 0, [this.curAttr, ' ']); @@ -2295,35 +2313,32 @@ Terminal.prototype.insertChars = function(params) { // CSI Ps E // Cursor Next Line Ps Times (default = 1) (CNL). +// same as CSI Ps B ? Terminal.prototype.cursorNextLine = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.y += param; if (this.y >= this.rows) { this.y = this.rows - 1; } - // above is the same as CSI Ps B this.x = 0; }; // CSI Ps F // Cursor Preceding Line Ps Times (default = 1) (CNL). +// reuse CSI Ps A ? Terminal.prototype.cursorPrecedingLine = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.y -= param; if (this.y < 0) this.y = 0; - // above is the same as CSI Ps A this.x = 0; }; // CSI Ps G // Cursor Character Absolute [column] (default = [row,1]) (CHA). Terminal.prototype.cursorCharAbsolute = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.x = param - 1; }; @@ -2332,6 +2347,7 @@ Terminal.prototype.cursorCharAbsolute = function(params) { // Insert Ps Line(s) (default = 1) (IL). Terminal.prototype.insertLines = function(params) { var param, row, j; + param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; @@ -2353,6 +2369,7 @@ Terminal.prototype.insertLines = function(params) { // Delete Ps Line(s) (default = 1) (DL). Terminal.prototype.deleteLines = function(params) { var param, row, j; + param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; @@ -2374,9 +2391,12 @@ Terminal.prototype.deleteLines = function(params) { // Delete Ps Character(s) (default = 1) (DCH). Terminal.prototype.deleteChars = function(params) { var param, row; + param = params[0]; if (param < 1) param = 1; + row = this.y + this.ybase; + while (param--) { this.lines[row].splice(this.x, 1); // xterm, linux: @@ -2388,10 +2408,13 @@ Terminal.prototype.deleteChars = function(params) { // Erase Ps Character(s) (default = 1) (ECH). Terminal.prototype.eraseChars = function(params) { var param, row, j; + param = params[0]; if (param < 1) param = 1; + row = this.y + this.ybase; j = this.x; + while (param-- && j < this.cols) { // xterm, linux: this.lines[row][j++] = [this.curAttr, ' ']; @@ -2401,8 +2424,7 @@ Terminal.prototype.eraseChars = function(params) { // CSI Pm ` Character Position Absolute // [column] (default = [row,1]) (HPA). Terminal.prototype.charPosAbsolute = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.x = param - 1; if (this.x >= this.cols) { @@ -2412,15 +2434,14 @@ Terminal.prototype.charPosAbsolute = function(params) { // 141 61 a * HPR - // Horizontal Position Relative +// reuse CSI Ps C ? Terminal.prototype.HPositionRelative = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.x += param; if (this.x >= this.cols - 1) { this.x = this.cols - 1; } - // above is the same as CSI Ps C }; // CSI Ps c Send Device Attributes (Primary DA). @@ -2475,8 +2496,7 @@ Terminal.prototype.sendDeviceAttributes = function(params) { // CSI Pm d // Line Position Absolute [row] (default = [1,column]) (VPA). Terminal.prototype.linePosAbsolute = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.y = param - 1; if (this.y >= this.rows) { @@ -2485,15 +2505,14 @@ Terminal.prototype.linePosAbsolute = function(params) { }; // 145 65 e * VPR - Vertical Position Relative +// reuse CSI Ps B ? Terminal.prototype.VPositionRelative = function(params) { - var param, row; - param = params[0]; + var param = params[0]; if (param < 1) param = 1; this.y += param; if (this.y >= this.rows) { this.y = this.rows - 1; } - // above is same as CSI Ps B }; // CSI Ps ; Ps f @@ -2641,7 +2660,7 @@ Terminal.prototype.setMode = function(params) { case 1003: // any event mouse // button press, release, wheel, and motion. // no modifiers except control. - console.log('binding to mouse events - warning: experimental!'); + this.log('Binding to mouse events.'); this.mouseEvents = true; this.element.style.cursor = 'default'; break; @@ -2912,7 +2931,7 @@ Terminal.prototype.scrollDown = function(params) { // [func;startx;starty;firstrow;lastrow]. See the section Mouse // Tracking. Terminal.prototype.initMouseTracking = function(params) { - console.log('mouse tracking'); + this.log('Enable Mouse Tracking'); }; // CSI > Ps; Ps T From bf345155742e55253beb77c7665d6350f43e3bb4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 05:19:28 -0500 Subject: [PATCH 010/131] small changes --- README.md | 2 -- static/term.js | 2 +- static/tty.js | 14 ++++++++------ 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index 969844c5..ff00cdd4 100644 --- a/README.md +++ b/README.md @@ -79,8 +79,6 @@ JSON file. An example configuration file looks like: "#ad7fa8", "#34e2e2", "#eeeeec", - "#000000", - "#f0f0f0" ] } } diff --git a/static/term.js b/static/term.js index 5208cae2..835cac12 100644 --- a/static/term.js +++ b/static/term.js @@ -1944,7 +1944,7 @@ Terminal.prototype.reverseIndex = function() { // ESC c Full Reset (RIS). Terminal.prototype.reset = function() { - Terminal.call(this, this.cols, this.rows, this.handler); + Terminal.call(this, this.cols, this.rows); }; /** diff --git a/static/tty.js b/static/tty.js index 1860359c..bd07446e 100644 --- a/static/tty.js +++ b/static/tty.js @@ -13,8 +13,9 @@ var doc = this.document , win = this , root , body - , h1 - , initialTitle = doc.title; + , h1; + +var initialTitle = doc.title; /** * Shared @@ -558,10 +559,10 @@ Tab.prototype._destroy = function() { win.destroy(); } - if (!windows.length) { - document.title = initialTitle; - if (h1) h1.innerHTML = initialTitle; - } + // if (!windows.length) { + // document.title = initialTitle; + // if (h1) h1.innerHTML = initialTitle; + // } }; Tab.prototype.destroy = function() { @@ -725,6 +726,7 @@ function sanitize(text) { */ function load() { + if (socket) return; off(doc, 'load', load); off(doc, 'DOMContentLoaded', load); open(); From c3bae5112486e28ff87e4f2611ab030d8e208ad9 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 05:26:28 -0500 Subject: [PATCH 011/131] v0.2.5 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index f9cd02db..4d110f27 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.0", + "version": "0.2.5", "main": "./lib/tty.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", @@ -15,6 +15,6 @@ "dependencies": { "express": ">= 2.5.8", "socket.io": ">= 0.8.7", - "pty.js": ">= 0.0.6" + "pty.js": ">= 0.0.7" } } From 11f2e16ae4dbadcac3375ebd3b02ea1fa40bef4b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 12:30:45 -0500 Subject: [PATCH 012/131] fix inverse colors --- static/term.js | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/static/term.js b/static/term.js index 835cac12..24a34941 100644 --- a/static/term.js +++ b/static/term.js @@ -175,15 +175,18 @@ Terminal.colors = function() { return colors; }(); -// save fallback -Terminal._colors = Terminal.colors; - // default bg/fg Terminal.defaultColors = { bg: '#000000', fg: '#f0f0f0' }; +Terminal.colors[256] = Terminal.defaultColors.bg; +Terminal.colors[257] = Terminal.defaultColors.fg; + +// save fallback +Terminal._colors = Terminal.colors.slice(); + Terminal.termName = ''; Terminal.geometry = [80, 30]; Terminal.cursorBlink = true; From 89a95a0964b2a74462528616cfcf64c599bb5f29 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 12:47:57 -0500 Subject: [PATCH 013/131] fix inverse. log and error functions. fix title setting. --- static/term.js | 16 ++++++++++++---- static/tty.js | 14 +++++++++----- 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/static/term.js b/static/term.js index 24a34941..5cfda940 100644 --- a/static/term.js +++ b/static/term.js @@ -30,6 +30,12 @@ 'use strict'; +/** + * Shared + */ + +var window = this; + /** * States */ @@ -1780,16 +1786,18 @@ Terminal.prototype.bell = function() { if (Terminal.popOnBell) this.focus(); }; -Terminal.prototype.log = function(data) { +Terminal.prototype.log = function() { if (!Terminal.debug) return; if (!window.console || !window.console.log) return; - window.console.log(data); + var args = Array.prototype.slice.call(arguments); + window.console.log.apply(window.console, args); }; -Terminal.prototype.error = function(data) { +Terminal.prototype.error = function() { if (!Terminal.debug) return; if (!window.console || !window.console.error) return; - window.console.error(data); + var args = Array.prototype.slice.call(arguments); + window.console.error.apply(window.console, args); }; Terminal.prototype.resize = function(x, y) { diff --git a/static/tty.js b/static/tty.js index bd07446e..f9b2b42a 100644 --- a/static/tty.js +++ b/static/tty.js @@ -483,13 +483,17 @@ Tab.prototype.handleTitle = function(title) { if (!title) return; title = sanitize(title); - document.title = title; - this.window.bar.title = title; - this.title = title; - // this.setProcessName(this.process); - // if (h1) h1.innerHTML = title; + if (Terminal.focus === this) { + document.title = title; + // if (h1) h1.innerHTML = title; + } + + if (this.window.focused === this) { + this.window.bar.title = title; + // this.setProcessName(this.process); + } }; Tab.prototype._write = Tab.prototype.write; From 2b04bc39af1acce3eac7ae29ccb9becd83447705 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 14:05:07 -0500 Subject: [PATCH 014/131] optimize character creation. --- static/term.js | 65 ++++++++++++++++++++++++++++---------------------- 1 file changed, 37 insertions(+), 28 deletions(-) diff --git a/static/term.js b/static/term.js index 5cfda940..5018edb5 100644 --- a/static/term.js +++ b/static/term.js @@ -955,7 +955,7 @@ Terminal.prototype.write = function(str) { default: this.state = normal; - this.error('Unknown ESC control: ' + ch + '.'); + this.error('Unknown ESC control: %s.', ch); break; } break; @@ -1804,7 +1804,8 @@ Terminal.prototype.resize = function(x, y) { var line , el , i - , j; + , j + , ch; if (x < 1) x = 1; if (y < 1) y = 1; @@ -1812,10 +1813,11 @@ Terminal.prototype.resize = function(x, y) { // resize cols j = this.cols; if (j < x) { + ch = [this.defAttr, ' ']; i = this.lines.length; while (i--) { while (this.lines[i].length < x) { - this.lines[i].push([this.defAttr, ' ']); + this.lines[i].push(ch); } } } else if (j > x) { @@ -1919,6 +1921,12 @@ Terminal.prototype.blankLine = function(cur) { return line; }; +Terminal.prototype.ch = function(cur) { + return cur + ? [this.curAttr, ' '] + : [this.defAttr, ' ']; +}; + Terminal.prototype.handler = function() {}; Terminal.prototype.handleTitle = function() {}; @@ -2307,17 +2315,17 @@ Terminal.prototype.deviceStatus = function(params) { // CSI Ps @ // Insert Ps (Blank) Character(s) (default = 1) (ICH). Terminal.prototype.insertChars = function(params) { - var param, row, j; + var param, row, j, ch; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; j = this.x; + ch = [this.curAttr, ' ']; // xterm while (param-- && j < this.cols) { - // xterm, linux: - this.lines[row].splice(j++, 0, [this.curAttr, ' ']); + this.lines[row].splice(j++, 0, ch); this.lines[row].pop(); } }; @@ -2401,34 +2409,34 @@ Terminal.prototype.deleteLines = function(params) { // CSI Ps P // Delete Ps Character(s) (default = 1) (DCH). Terminal.prototype.deleteChars = function(params) { - var param, row; + var param, row, ch; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; + ch = [this.curAttr, ' ']; // xterm while (param--) { this.lines[row].splice(this.x, 1); - // xterm, linux: - this.lines[row].push([this.curAttr, ' ']); + this.lines[row].push(ch); } }; // CSI Ps X // Erase Ps Character(s) (default = 1) (ECH). Terminal.prototype.eraseChars = function(params) { - var param, row, j; + var param, row, j, ch; param = params[0]; if (param < 1) param = 1; row = this.y + this.ybase; j = this.x; + ch = [this.curAttr, ' ']; // xterm while (param-- && j < this.cols) { - // xterm, linux: - this.lines[row][j++] = [this.curAttr, ' ']; + this.lines[row][j++] = ch; } }; @@ -2981,9 +2989,10 @@ Terminal.prototype.cursorBackwardTab = function(params) { // CSI Ps b Repeat the preceding graphic character Ps times (REP). Terminal.prototype.repeatPrecedingCharacter = function(params) { - var param = params[0] || 1; - var line = this.lines[this.ybase + this.y]; - var ch = line[this.x - 1] || [this.defAttr, ' ']; + var param = params[0] || 1 + , line = this.lines[this.ybase + this.y] + , ch = line[this.x - 1] || [this.defAttr, ' ']; + while (param--) line[this.x++] = ch; }; @@ -3355,13 +3364,15 @@ Terminal.prototype.eraseRectangle = function(params) { , r = params[3]; var line - , i; + , i + , ch; + + ch = [this.curAttr, ' ']; // xterm? for (; t < b + 1; t++) { line = this.lines[this.ybase + t]; for (i = l; i < r; i++) { - // curAttr for xterm behavior? - line[i] = [this.curAttr, ' ']; + line[i] = ch; } } }; @@ -3436,15 +3447,14 @@ Terminal.prototype.requestLocatorPosition = function(params) { // Insert P s Column(s) (default = 1) (DECIC), VT420 and up. // NOTE: xterm doesn't enable this code by default. Terminal.prototype.insertColumns = function() { - param = params[0]; - - var l = this.ybase + this.rows + var param = params[0] + , l = this.ybase + this.rows + , ch = [this.curAttr, ' '] // xterm? , i; while (param--) { for (i = this.ybase; i < l; i++) { - // xterm behavior uses curAttr? - this.lines[i].splice(this.x + 1, 0, [this.curAttr, ' ']); + this.lines[i].splice(this.x + 1, 0, ch); this.lines[i].pop(); } } @@ -3454,16 +3464,15 @@ Terminal.prototype.insertColumns = function() { // Delete P s Column(s) (default = 1) (DECDC), VT420 and up // NOTE: xterm doesn't enable this code by default. Terminal.prototype.deleteColumns = function() { - param = params[0]; - - var l = this.ybase + this.rows + var param = params[0] + , l = this.ybase + this.rows + , ch = [this.curAttr, ' '] // xterm? , i; while (param--) { for (i = this.ybase; i < l; i++) { this.lines[i].splice(this.x, 1); - // xterm behavior uses curAttr? - this.lines[i].push([this.curAttr, ' ']); + this.lines[i].push(ch); } } }; From 1be0c55df24ae6e074cbfe2077947d45eaa9eda3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 16:40:31 -0500 Subject: [PATCH 015/131] fix regression involving getRows/updateRange. refactor. --- static/term.js | 58 ++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 42 insertions(+), 16 deletions(-) diff --git a/static/term.js b/static/term.js index 5018edb5..fa4a0d30 100644 --- a/static/term.js +++ b/static/term.js @@ -723,6 +723,8 @@ Terminal.prototype.scroll = function() { } this.lines.splice(this.ybase + this.scrollTop, 1); } + + this.maxRange(); }; Terminal.prototype.scrollDisp = function(disp) { @@ -779,7 +781,6 @@ Terminal.prototype.write = function(str) { if (this.y >= this.scrollBottom + 1) { this.y--; this.scroll(); - this.maxRange(); } break; @@ -821,13 +822,12 @@ Terminal.prototype.write = function(str) { if (this.y >= this.scrollBottom + 1) { this.y--; this.scroll(); - this.maxRange(); } } row = this.y + this.ybase; this.lines[row][this.x] = [this.curAttr, ch]; this.x++; - this.updateRange(); + this.updateRange(this.y); } break; } @@ -1505,12 +1505,13 @@ Terminal.prototype.write = function(str) { this.prefix = ''; this.postfix = ''; - this.updateRange(); + // ensure any line changes update + this.updateRange(this.y); break; } } - this.updateRange(); + this.updateRange(this.y); if (this.refreshEnd >= this.refreshStart) { this.refresh(this.refreshStart, this.refreshEnd); @@ -1874,9 +1875,9 @@ Terminal.prototype.resize = function(x, y) { this.normal = null; }; -Terminal.prototype.updateRange = function() { - this.refreshStart = Math.min(this.refreshStart, this.y); - this.refreshEnd = Math.max(this.refreshEnd, this.y); +Terminal.prototype.updateRange = function(y) { + if (y < this.refreshStart) this.refreshStart = y; + if (y > this.refreshEnd) this.refreshEnd = y; }; Terminal.prototype.maxRange = function() { @@ -1886,19 +1887,23 @@ Terminal.prototype.maxRange = function() { Terminal.prototype.eraseRight = function(x, y) { var line = this.lines[this.ybase + y] - , ch = [this.curAttr, ' ']; + , ch = [this.curAttr, ' ']; // xterm for (; x < this.cols; x++) { line[x] = ch; } + + this.updateRange(y); }; Terminal.prototype.eraseLeft = function(x, y) { var line = this.lines[this.ybase + y] - , ch = [this.curAttr, ' ']; + , ch = [this.curAttr, ' ']; // xterm x++; while (x--) line[x] = ch; + + this.updateRange(y); }; Terminal.prototype.eraseLine = function(y) { @@ -1940,7 +1945,6 @@ Terminal.prototype.index = function() { if (this.y >= this.scrollBottom + 1) { this.y--; this.scroll(); - this.maxRange(); } this.state = normal; }; @@ -1951,6 +1955,7 @@ Terminal.prototype.reverseIndex = function() { this.y--; if (this.y < this.scrollTop) { this.y++; + // possibly move the code below to term.reverseScroll(); // test: echo -ne '\e[1;1H\e[44m\eM\e[0m' // blankLine(true) is xterm/linux behavior this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); @@ -2381,7 +2386,8 @@ Terminal.prototype.insertLines = function(params) { this.lines.splice(j, 1); } - this.maxRange(); + // this.maxRange(); + this.updateRange(this.scrollBottom); }; // CSI Ps M @@ -2403,7 +2409,8 @@ Terminal.prototype.deleteLines = function(params) { this.lines.splice(row, 1); } - this.maxRange(); + // this.maxRange(); + this.updateRange(this.scrollBottom); }; // CSI Ps P @@ -2932,7 +2939,9 @@ Terminal.prototype.scrollUp = function(params) { this.lines.splice(this.ybase + this.scrollTop, 1); this.lines.splice(this.ybase + this.scrollBottom, 0, this.blankLine()); } - this.maxRange(); + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); }; // CSI Ps T Scroll down Ps lines (default = 1) (SD). @@ -2942,7 +2951,9 @@ Terminal.prototype.scrollDown = function(params) { this.lines.splice(this.ybase + this.scrollBottom, 1); this.lines.splice(this.ybase + this.scrollTop, 0, this.blankLine()); } - this.maxRange(); + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); }; // CSI Ps ; Ps ; Ps ; Ps ; Ps T @@ -3166,6 +3177,10 @@ Terminal.prototype.setAttrInRectangle = function(params) { line[i] = [attr, line[i][1]]; } } + + // this.maxRange(); + this.updateRange(params[0]); + this.updateRange(params[2]); }; // CSI ? Pm s @@ -3335,6 +3350,10 @@ Terminal.prototype.fillRectangle = function(params) { line[i] = [line[i][0], String.fromCharCode(ch)]; } } + + // this.maxRange(); + this.updateRange(params[1]); + this.updateRange(params[3]); }; // CSI Ps ; Pu ' z @@ -3375,6 +3394,10 @@ Terminal.prototype.eraseRectangle = function(params) { line[i] = ch; } } + + // this.maxRange(); + this.updateRange(params[0]); + this.updateRange(params[2]); }; // CSI Pm ' { @@ -3458,6 +3481,8 @@ Terminal.prototype.insertColumns = function() { this.lines[i].pop(); } } + + this.maxRange(); }; // CSI P m SP ~ @@ -3475,6 +3500,8 @@ Terminal.prototype.deleteColumns = function() { this.lines[i].push(ch); } } + + this.maxRange(); }; /** @@ -3559,7 +3586,6 @@ function isBoldBroken() { } var String = this.String; -var Math = this.Math; /** * Expose From 20c6d07f2f6ed55f981a013ccec915c0ca6c6697 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Apr 2012 18:36:44 -0500 Subject: [PATCH 016/131] refactor scrollBottom and cols checks --- static/term.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/static/term.js b/static/term.js index fa4a0d30..8bc7d9a8 100644 --- a/static/term.js +++ b/static/term.js @@ -754,7 +754,7 @@ Terminal.prototype.write = function(str) { this.maxRange(); } - // console.log(JSON.stringify(str.replace(/\x1b/g, '^['))); + // this.log(JSON.stringify(str.replace(/\x1b/g, '^['))); for (; i < l; i++) { ch = str[i]; @@ -778,7 +778,7 @@ Terminal.prototype.write = function(str) { this.x = 0; } this.y++; - if (this.y >= this.scrollBottom + 1) { + if (this.y > this.scrollBottom) { this.y--; this.scroll(); } @@ -819,7 +819,7 @@ Terminal.prototype.write = function(str) { if (this.x >= this.cols) { this.x = 0; this.y++; - if (this.y >= this.scrollBottom + 1) { + if (this.y > this.scrollBottom) { this.y--; this.scroll(); } @@ -1942,7 +1942,7 @@ Terminal.prototype.handleTitle = function() {}; // ESC D Index (IND is 0x84). Terminal.prototype.index = function() { this.y++; - if (this.y >= this.scrollBottom + 1) { + if (this.y > this.scrollBottom) { this.y--; this.scroll(); } @@ -2001,7 +2001,7 @@ Terminal.prototype.cursorForward = function(params) { var param = params[0]; if (param < 1) param = 1; this.x += param; - if (this.x >= this.cols - 1) { + if (this.x >= this.cols) { this.x = this.cols - 1; } }; @@ -2465,7 +2465,7 @@ Terminal.prototype.HPositionRelative = function(params) { var param = params[0]; if (param < 1) param = 1; this.x += param; - if (this.x >= this.cols - 1) { + if (this.x >= this.cols) { this.x = this.cols - 1; } }; From d23d063539e921077a14750ff2ee69bf1f169eb4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 5 Apr 2012 02:15:48 -0500 Subject: [PATCH 017/131] remove comments --- static/term.js | 2 -- 1 file changed, 2 deletions(-) diff --git a/static/term.js b/static/term.js index 8bc7d9a8..7c56ee91 100644 --- a/static/term.js +++ b/static/term.js @@ -1066,7 +1066,6 @@ Terminal.prototype.write = function(str) { // 0 - 9 if (ch >= '0' && ch <= '9') { this.currentParam = this.currentParam * 10 + ch.charCodeAt(0) - 48; - // this.currentParam += ch; break; } @@ -1077,7 +1076,6 @@ Terminal.prototype.write = function(str) { } this.params.push(this.currentParam); - // this.params.push(+this.currentParam); this.currentParam = 0; // ';' From 8f295c603d529a507e3e5f00e6549f1735271a23 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 5 Apr 2012 02:32:17 -0500 Subject: [PATCH 018/131] refactor key events --- static/term.js | 70 +++++++++++++++++--------------------------------- 1 file changed, 23 insertions(+), 47 deletions(-) diff --git a/static/term.js b/static/term.js index 7c56ee91..bcde9934 100644 --- a/static/term.js +++ b/static/term.js @@ -78,8 +78,6 @@ var Terminal = function(cols, rows, handler) { this.defAttr = (257 << 9) | 256; this.curAttr = this.defAttr; - this.keyState = 0; - this.keyStr = ''; this.params = []; this.currentParam = 0; @@ -1522,6 +1520,7 @@ Terminal.prototype.writeln = function(str) { Terminal.prototype.keyDownHandler = function(ev) { var str = ''; + switch (ev.keyCode) { // backspace case 8: @@ -1705,74 +1704,49 @@ Terminal.prototype.keyDownHandler = function(ev) { } if (str) { - cancel(ev); - this.showCursor(); - this.keyState = 1; - this.keyStr = str; this.handler(str); - - return false; - } else { - this.keyState = 0; - return true; + return cancel(ev); } + + return true; }; Terminal.prototype.keyPressHandler = function(ev) { - var str = '' - , key; + var key; cancel(ev); - if (!('charCode' in ev)) { + if (ev.charCode) { + key = ev.charCode; + } else if (ev.which == null) { key = ev.keyCode; - if (this.keyState === 1) { - this.keyState = 2; - return false; - } else if (this.keyState === 2) { - this.showCursor(); - this.handler(this.keyStr); - return false; - } + } else if (ev.which !== 0 && ev.charCode !== 0) { + key = ev.which; } else { - key = ev.charCode; + return false; } - if (key !== 0) { - if (!ev.ctrlKey - && ((!isMac && !ev.altKey) - || (isMac && !ev.metaKey))) { - str = String.fromCharCode(key); - } - } + if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) return false; - if (str) { - this.showCursor(); - this.handler(str); - return false; - } else { - return true; - } + this.showCursor(); + this.handler(String.fromCharCode(key)); + + return false; }; + Terminal.prototype.queueChars = function(str) { var self = this; - this.outputQueue += str; - - if (this.outputQueue) { + if (!this.outputQueue) { setTimeout(function() { - self.outputHandler(); + self.handler(self.outputQueue); + self.outputQueue = ''; }, 1); } -}; -Terminal.prototype.outputHandler = function() { - if (this.outputQueue) { - this.handler(this.outputQueue); - this.outputQueue = ''; - } + this.outputQueue += str; }; Terminal.prototype.bell = function() { @@ -3584,6 +3558,8 @@ function isBoldBroken() { } var String = this.String; +var setTimeout = this.setTimeout; +var setInterval = this.setInterval; /** * Expose From 3dd20f8a31ec6dd1a616b978c7db2b7fc7d6818f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 5 Apr 2012 03:53:55 -0500 Subject: [PATCH 019/131] refactor. rename variables, functions. --- static/term.js | 158 +++++++++++++++++++++++++------------------------ static/tty.js | 8 +-- 2 files changed, 84 insertions(+), 82 deletions(-) diff --git a/static/term.js b/static/term.js index bcde9934..ed1d8dfa 100644 --- a/static/term.js +++ b/static/term.js @@ -63,7 +63,7 @@ var Terminal = function(cols, rows, handler) { this.cursorHidden = false; this.convertEol = false; this.state = 0; - this.outputQueue = ''; + this.queue = ''; this.scrollTop = 0; this.scrollBottom = this.rows - 1; @@ -225,11 +225,11 @@ Terminal.bindKeys = function() { // We could put an "if (Term.focus)" check // here, but it shouldn't be necessary. on(document, 'keydown', function(key) { - return Terminal.focus.keyDownHandler(key); + return Terminal.focus.keyDown(key); }, true); on(document, 'keypress', function(key) { - return Terminal.focus.keyPressHandler(key); + return Terminal.focus.keyPress(key); }, true); }; @@ -290,9 +290,9 @@ Terminal.prototype.open = function() { on(this.element, 'paste', function(ev) { if (ev.clipboardData) { - self.queueChars(ev.clipboardData.getData('text/plain')); + self.send(ev.clipboardData.getData('text/plain')); } else if (window.clipboardData) { - self.queueChars(window.clipboardData.getData('Text')); + self.send(window.clipboardData.getData('Text')); } // Not necessary. Do it anyway for good measure. self.element.contentEditable = 'inherit'; @@ -380,7 +380,7 @@ Terminal.prototype.bindMouse = function() { // send a mouse event: // ^[[M Cb Cx Cy function sendEvent(button, pos) { - self.queueChars('\x1b[M' + String.fromCharCode(button, pos.x, pos.y)); + self.send('\x1b[M' + String.fromCharCode(button, pos.x, pos.y)); } function getButton(ev) { @@ -572,8 +572,9 @@ Terminal.prototype.refresh = function(start, end) { } attr = this.defAttr; + i = 0; - for (i = 0; i < width; i++) { + for (; i < width; i++) { data = line[i][0]; ch = line[i][1]; @@ -737,8 +738,8 @@ Terminal.prototype.scrollDisp = function(disp) { this.refresh(0, this.rows - 1); }; -Terminal.prototype.write = function(str) { - var l = str.length +Terminal.prototype.write = function(data) { + var l = data.length , i = 0 , ch , param @@ -752,10 +753,10 @@ Terminal.prototype.write = function(str) { this.maxRange(); } - // this.log(JSON.stringify(str.replace(/\x1b/g, '^['))); + // this.log(JSON.stringify(data.replace(/\x1b/g, '^['))); for (; i < l; i++) { - ch = str[i]; + ch = data[i]; switch (this.state) { case normal: switch (ch) { @@ -1514,97 +1515,97 @@ Terminal.prototype.write = function(str) { } }; -Terminal.prototype.writeln = function(str) { - this.write(str + '\r\n'); +Terminal.prototype.writeln = function(data) { + this.write(data + '\r\n'); }; -Terminal.prototype.keyDownHandler = function(ev) { - var str = ''; +Terminal.prototype.keyDown = function(ev) { + var key; switch (ev.keyCode) { // backspace case 8: - str = '\x7f'; // ^? - //str = '\x08'; // ^H + key = '\x7f'; // ^? + //key = '\x08'; // ^H break; // tab case 9: - str = '\t'; + key = '\t'; break; // return/enter case 13: - str = '\r'; + key = '\r'; break; // escape case 27: - str = '\x1b'; + key = '\x1b'; break; // left-arrow case 37: if (this.applicationKeypad) { - str = '\x1bOD'; // SS3 as ^[O for 7-bit - //str = '\x8fD'; // SS3 as 0x8f for 8-bit + key = '\x1bOD'; // SS3 as ^[O for 7-bit + //key = '\x8fD'; // SS3 as 0x8f for 8-bit break; } - str = '\x1b[D'; + key = '\x1b[D'; break; // right-arrow case 39: if (this.applicationKeypad) { - str = '\x1bOC'; + key = '\x1bOC'; break; } - str = '\x1b[C'; + key = '\x1b[C'; break; // up-arrow case 38: if (this.applicationKeypad) { - str = '\x1bOA'; + key = '\x1bOA'; break; } if (ev.ctrlKey) { this.scrollDisp(-1); return cancel(ev); } else { - str = '\x1b[A'; + key = '\x1b[A'; } break; // down-arrow case 40: if (this.applicationKeypad) { - str = '\x1bOB'; + key = '\x1bOB'; break; } if (ev.ctrlKey) { this.scrollDisp(1); return cancel(ev); } else { - str = '\x1b[B'; + key = '\x1b[B'; } break; // delete case 46: - str = '\x1b[3~'; + key = '\x1b[3~'; break; // insert case 45: - str = '\x1b[2~'; + key = '\x1b[2~'; break; // home case 36: if (this.applicationKeypad) { - str = '\x1bOH'; + key = '\x1bOH'; break; } - str = '\x1bOH'; + key = '\x1bOH'; break; // end case 35: if (this.applicationKeypad) { - str = '\x1bOF'; + key = '\x1bOF'; break; } - str = '\x1bOF'; + key = '\x1bOF'; break; // page up case 33: @@ -1612,7 +1613,7 @@ Terminal.prototype.keyDownHandler = function(ev) { this.scrollDisp(-(this.rows - 1)); return cancel(ev); } else { - str = '\x1b[5~'; + key = '\x1b[5~'; } break; // page down @@ -1621,98 +1622,98 @@ Terminal.prototype.keyDownHandler = function(ev) { this.scrollDisp(this.rows - 1); return cancel(ev); } else { - str = '\x1b[6~'; + key = '\x1b[6~'; } break; // F1 case 112: - str = '\x1bOP'; + key = '\x1bOP'; break; // F2 case 113: - str = '\x1bOQ'; + key = '\x1bOQ'; break; // F3 case 114: - str = '\x1bOR'; + key = '\x1bOR'; break; // F4 case 115: - str = '\x1bOS'; + key = '\x1bOS'; break; // F5 case 116: - str = '\x1b[15~'; + key = '\x1b[15~'; break; // F6 case 117: - str = '\x1b[17~'; + key = '\x1b[17~'; break; // F7 case 118: - str = '\x1b[18~'; + key = '\x1b[18~'; break; // F8 case 119: - str = '\x1b[19~'; + key = '\x1b[19~'; break; // F9 case 120: - str = '\x1b[20~'; + key = '\x1b[20~'; break; // F10 case 121: - str = '\x1b[21~'; + key = '\x1b[21~'; break; // F11 case 122: - str = '\x1b[23~'; + key = '\x1b[23~'; break; // F12 case 123: - str = '\x1b[24~'; + key = '\x1b[24~'; break; default: // a-z and space if (ev.ctrlKey) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { - str = String.fromCharCode(ev.keyCode - 64); + key = String.fromCharCode(ev.keyCode - 64); } else if (ev.keyCode === 32) { // NUL - str = String.fromCharCode(0); + key = String.fromCharCode(0); } else if (ev.keyCode >= 51 && ev.keyCode <= 55) { // escape, file sep, group sep, record sep, unit sep - str = String.fromCharCode(ev.keyCode - 51 + 27); + key = String.fromCharCode(ev.keyCode - 51 + 27); } else if (ev.keyCode === 56) { // delete - str = String.fromCharCode(127); + key = String.fromCharCode(127); } else if (ev.keyCode === 219) { // ^[ - escape - str = String.fromCharCode(27); + key = String.fromCharCode(27); } else if (ev.keyCode === 221) { // ^] - group sep - str = String.fromCharCode(29); + key = String.fromCharCode(29); } } else if ((!isMac && ev.altKey) || (isMac && ev.metaKey)) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { - str = '\x1b' + String.fromCharCode(ev.keyCode + 32); + key = '\x1b' + String.fromCharCode(ev.keyCode + 32); } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { - str = '\x1b' + (ev.keyCode - 48); + key = '\x1b' + (ev.keyCode - 48); } } break; } - if (str) { + if (key) { this.showCursor(); - this.handler(str); + this.handler(key); return cancel(ev); } return true; }; -Terminal.prototype.keyPressHandler = function(ev) { +Terminal.prototype.keyPress = function(ev) { var key; cancel(ev); @@ -1729,24 +1730,25 @@ Terminal.prototype.keyPressHandler = function(ev) { if (!key || ev.ctrlKey || ev.altKey || ev.metaKey) return false; + key = String.fromCharCode(key); + this.showCursor(); - this.handler(String.fromCharCode(key)); + this.handler(key); return false; }; - -Terminal.prototype.queueChars = function(str) { +Terminal.prototype.send = function(data) { var self = this; - if (!this.outputQueue) { + if (!this.queue) { setTimeout(function() { - self.handler(self.outputQueue); - self.outputQueue = ''; + self.handler(self.queue); + self.queue = ''; }, 1); } - this.outputQueue += str; + this.queue += data; }; Terminal.prototype.bell = function() { @@ -1990,7 +1992,7 @@ Terminal.prototype.cursorBackward = function(params) { // CSI Ps ; Ps H // Cursor Position [row;column] (default = [1,1]) (CUP). Terminal.prototype.cursorPos = function(params) { - var param, row, col; + var row, col; row = params[0] - 1; @@ -2247,7 +2249,7 @@ Terminal.prototype.deviceStatus = function(params) { // respond to any of these except ?6, 6, and 5 switch (params[0]) { case 6: - this.queueChars('\x1b[' + this.send('\x1b[' + (this.y + 1) + ';' + (this.x + 1) @@ -2255,28 +2257,28 @@ Terminal.prototype.deviceStatus = function(params) { break; case 15: // no printer - // this.queueChars('\x1b[?11n'); + // this.send('\x1b[?11n'); break; case 25: // dont support user defined keys - // this.queueChars('\x1b[?21n'); + // this.send('\x1b[?21n'); break; case 26: - // this.queueChars('\x1b[?27;1;0;0n'); + // this.send('\x1b[?27;1;0;0n'); break; case 53: // no dec locator/mouse - // this.queueChars('\x1b[?50n'); + // this.send('\x1b[?50n'); break; } return; } switch (params[0]) { case 5: - this.queueChars('\x1b[0n'); + this.send('\x1b[0n'); break; case 6: - this.queueChars('\x1b[' + this.send('\x1b[' + (this.y + 1) + ';' + (this.x + 1) @@ -2481,13 +2483,13 @@ Terminal.prototype.sendDeviceAttributes = function(params) { return; if (this.prefix !== '>') { - this.queueChars('\x1b[?1;2c'); + this.send('\x1b[?1;2c'); } else { // say we're a vt100 with // firmware version 95 - // this.queueChars('\x1b[>0;95;0c'); + // this.send('\x1b[>0;95;0c'); // modern xterm responds with: - this.queueChars('\x1b[>0;276;0c'); + this.send('\x1b[>0;276;0c'); } }; diff --git a/static/tty.js b/static/tty.js index f9b2b42a..3a0ed8f0 100644 --- a/static/tty.js +++ b/static/tty.js @@ -575,9 +575,9 @@ Tab.prototype.destroy = function() { this._destroy(); }; -Tab.prototype._keyDownHandler = Tab.prototype.keyDownHandler; +Tab.prototype._keyDown = Tab.prototype.keyDown; -Tab.prototype.keyDownHandler = function(ev) { +Tab.prototype.keyDown = function(ev) { if (this.pendingKey) { this.pendingKey = false; return this.specialKeyHandler(ev); @@ -617,7 +617,7 @@ Tab.prototype.keyDownHandler = function(ev) { } // Pass to terminal key handler. - return this._keyDownHandler(ev); + return this._keyDown(ev); }; // tmux/screen-like keys @@ -628,7 +628,7 @@ Tab.prototype.specialKeyHandler = function(ev) { switch (key) { case 65: // a if (ev.ctrlKey) { - return this._keyDownHandler(ev); + return this._keyDown(ev); } break; case 67: // c From ae22f007f4b08c6d700d2c451c46dff082ea39b4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 00:21:26 -0500 Subject: [PATCH 020/131] add experimental program-specific features. refactor term. --- static/term.js | 16 +++++++++------- static/tty.js | 46 +++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 54 insertions(+), 8 deletions(-) diff --git a/static/term.js b/static/term.js index ed1d8dfa..954a77d1 100644 --- a/static/term.js +++ b/static/term.js @@ -50,7 +50,7 @@ var normal = 0 * Terminal */ -var Terminal = function(cols, rows, handler) { +function Terminal(cols, rows, handler) { this.cols = cols; this.rows = rows; if (handler) this.handler = handler; @@ -87,7 +87,7 @@ var Terminal = function(cols, rows, handler) { while (i--) { this.lines.push(this.blankLine()); } -}; +} /** * Options @@ -198,6 +198,8 @@ Terminal.visualBell = false; Terminal.popOnBell = false; Terminal.scrollback = 1000; Terminal.screenKeys = false; +Terminal.debug = false; +Terminal.programFeatures = false; /** * Focused Terminal @@ -222,14 +224,14 @@ Terminal.prototype.focus = function() { Terminal.bindKeys = function() { if (Terminal.focus) return; - // We could put an "if (Term.focus)" check + // We could put an "if (Terminal.focus)" check // here, but it shouldn't be necessary. - on(document, 'keydown', function(key) { - return Terminal.focus.keyDown(key); + on(document, 'keydown', function(ev) { + return Terminal.focus.keyDown(ev); }, true); - on(document, 'keypress', function(key) { - return Terminal.focus.keyPress(key); + on(document, 'keypress', function(ev) { + return Terminal.focus.keyPress(ev); }, true); }; diff --git a/static/tty.js b/static/tty.js index 3a0ed8f0..b5c8c400 100644 --- a/static/tty.js +++ b/static/tty.js @@ -10,7 +10,7 @@ */ var doc = this.document - , win = this + , window = this , root , body , h1; @@ -657,6 +657,50 @@ Tab.prototype.specialKeyHandler = function(ev) { return cancel(ev); }; +/** + * Program-specific Features + */ + +Tab.prototype._bindMouse = Tab.prototype.bindMouse; +Tab.prototype.bindMouse = function() { + if (!Terminal.programFeatures) return this._bindMouse(); + + var self = this; + + var wheelEvent = 'onmousewheel' in window + ? 'mousewheel' + : 'DOMMouseScroll'; + + var programs = { + irssi: true, + man: true, + less: true, + htop: true, + top: true, + w3m: true, + lynx: true + }; + + // Mouse support for Irssi. + on(self.element, wheelEvent, function(ev) { + if (self.mouseEvents) return; + if (!programs[self.process]) return; + + if ((ev.type === 'mousewheel' && ev.wheelDeltaY > 0) + || (ev.type === 'DOMMouseScroll' && ev.detail < 0)) { + // page up + self.keyDown({keyCode: 33}); + } else { + // page down + self.keyDown({keyCode: 34}); + } + + return cancel(ev); + }); + + return this._bindMouse(); +}; + Tab.prototype.pollProcessName = function(func) { var self = this; socket.emit('process', this.id, function(err, name) { From 4e7d1fbe7f33c3d83c9970d09a9b20439e003489 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 05:16:57 -0500 Subject: [PATCH 021/131] improve and enable csi ps c. improve prefix checks. --- lib/config.js | 2 +- lib/tty.js | 9 +++++++- static/term.js | 60 +++++++++++++++++++++++++++++++++----------------- 3 files changed, 49 insertions(+), 22 deletions(-) diff --git a/lib/config.js b/lib/config.js index 53be6942..1b151d9a 100644 --- a/lib/config.js +++ b/lib/config.js @@ -109,13 +109,13 @@ function readConfig(name) { conf.shellArgs = conf.shellArgs || []; // $TERM + conf.term.termName = conf.termName || conf.term.termName; if (!conf.term.termName) { // tput -Txterm-256color longname conf.term.termName = exists('/usr/share/terminfo/x/xterm+256color') ? 'xterm-256color' : 'xterm'; } - conf.term.termName = conf.termName || conf.term.termName; conf.termName = conf.term.termName; // limits diff --git a/lib/tty.js b/lib/tty.js index d7aa83da..a51855bb 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -75,8 +75,15 @@ app.get('/options.js', function(req, res, next) { } catch(e) { data = {}; } + + if (data.term) { + Object.keys(data).forEach(function(key) { + conf.term[key] = data.term[key]; + }); + } + res.send('Terminal.options = ' - + JSON.stringify(data.term || {}) + + JSON.stringify(conf.term, null, 2) + ';\n' + '(' + applyConfig diff --git a/static/term.js b/static/term.js index 954a77d1..f0d29550 100644 --- a/static/term.js +++ b/static/term.js @@ -191,15 +191,15 @@ Terminal.colors[257] = Terminal.defaultColors.fg; // save fallback Terminal._colors = Terminal.colors.slice(); -Terminal.termName = ''; +Terminal.termName = 'xterm'; Terminal.geometry = [80, 30]; Terminal.cursorBlink = true; Terminal.visualBell = false; Terminal.popOnBell = false; Terminal.scrollback = 1000; Terminal.screenKeys = false; -Terminal.debug = false; Terminal.programFeatures = false; +Terminal.debug = false; /** * Focused Terminal @@ -1908,6 +1908,11 @@ Terminal.prototype.ch = function(cur) { : [this.defAttr, ' ']; }; +Terminal.prototype.is = function(term) { + var name = this.termName || Terminal.termName; + return (name + '').indexOf(term) === 0; +}; + Terminal.prototype.handler = function() {}; Terminal.prototype.handleTitle = function() {}; @@ -2478,20 +2483,35 @@ Terminal.prototype.HPositionRelative = function(params) { // the XFree86 patch number, starting with 95). In a DEC termi- // nal, Pc indicates the ROM cartridge registration number and is // always zero. +// More information: +// xterm/charproc.c - line 2012, for more information. +// vim responds with ^[[?0c or ^[[?1c after the terminal's response (?) Terminal.prototype.sendDeviceAttributes = function(params) { - // This severely breaks things if - // TERM is set to `linux`. xterm - // is fine. - return; - - if (this.prefix !== '>') { - this.send('\x1b[?1;2c'); - } else { - // say we're a vt100 with - // firmware version 95 - // this.send('\x1b[>0;95;0c'); - // modern xterm responds with: - this.send('\x1b[>0;276;0c'); + if (params[0] > 0) return; + + if (!this.prefix) { + if (this.is('xterm') + || this.is('rxvt-unicode') + || this.is('screen')) { + this.send('\x1b[?1;2c'); + } else if (this.is('linux')) { + this.send('\x1b[?6c'); + } + } else if (this.prefix === '>') { + // xterm and urxvt + // seem to spit this + // out around ~370 times (?). + if (this.is('xterm')) { + this.send('\x1b[>0;276;0c'); + } else if (this.is('rxvt-unicode')) { + this.send('\x1b[>85;95;0c'); + } else if (this.is('linux')) { + // not supported by linux console. + // linux console echoes parameters. + this.send(params[0] + 'c'); + } else if (this.is('screen')) { + this.send('\x1b[>83;40003;0c'); + } } }; @@ -2625,7 +2645,7 @@ Terminal.prototype.setMode = function(params) { return; } - if (this.prefix !== '?') { + if (!this.prefix) { switch (params) { case 4: this.insertMode = true; @@ -2634,7 +2654,7 @@ Terminal.prototype.setMode = function(params) { //this.convertEol = true; break; } - } else { + } else if (this.prefix === '?') { switch (params) { case 1: this.applicationKeypad = true; @@ -2799,7 +2819,7 @@ Terminal.prototype.resetMode = function(params) { return; } - if (this.prefix !== '?') { + if (!this.prefix) { switch (params) { case 4: this.insertMode = false; @@ -2808,7 +2828,7 @@ Terminal.prototype.resetMode = function(params) { //this.convertEol = false; break; } - } else { + } else if (this.prefix === '?') { switch (params) { case 1: this.applicationKeypad = false; @@ -2862,7 +2882,7 @@ Terminal.prototype.resetMode = function(params) { // dow) (DECSTBM). // CSI ? Pm r Terminal.prototype.setScrollRegion = function(params) { - if (this.prefix === '?') return; + if (this.prefix) return; this.scrollTop = (params[0] || 1) - 1; this.scrollBottom = (params[1] || this.rows) - 1; this.x = 0; From ee35641f2b23558669209c698999a06246acccec Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 06:29:27 -0500 Subject: [PATCH 022/131] send focus events --- static/term.js | 28 +++++++++++++++++++++------- 1 file changed, 21 insertions(+), 7 deletions(-) diff --git a/static/term.js b/static/term.js index f0d29550..2962a539 100644 --- a/static/term.js +++ b/static/term.js @@ -72,6 +72,7 @@ function Terminal(cols, rows, handler) { this.insertMode = false; this.wraparoundMode = false; this.mouseEvents; + this.sendFocus; this.tabs = []; this.charset = null; this.normal = null; @@ -212,8 +213,10 @@ Terminal.prototype.focus = function() { if (Terminal.focus) { Terminal.focus.cursorState = 0; Terminal.focus.refresh(Terminal.focus.y, Terminal.focus.y); + if (Terminal.focus.sendFocus) Terminal.focus.send('\x1b[>O'); } Terminal.focus = this; + if (this.sendFocus) this.send('\x1b[>I'); this.showCursor(); }; @@ -2689,6 +2692,7 @@ Terminal.prototype.setMode = function(params) { case 1004: // send focusin/focusout events // focusin: ^[[>I // focusout: ^[[>O + this.sendFocus = true; break; case 1005: // utf8 ext mode mouse // for wide terminals @@ -2839,16 +2843,26 @@ Terminal.prototype.resetMode = function(params) { case 7: this.wraparoundMode = false; break; - case 9: - case 1000: - case 1001: - case 1002: - case 1003: - case 1004: - case 1005: + case 9: // X10 Mouse + break; + case 1000: // vt200 mouse + break; + case 1001: // vt200 highlight mouse + break; + case 1002: // button event mouse + case 1003: // any event mouse this.mouseEvents = false; this.element.style.cursor = ''; break; + case 1004: // send focusin/focusout events + this.sendFocus = false; + break; + case 1005: // utf8 ext mode mouse + break; + case 1006: // sgr ext mode mouse + break; + case 1015: // urxvt ext mode mouse + break; case 25: // hide cursor this.cursorHidden = true; break; From ddb1f9a4a0e46b9c03f09a84a22fc53d19b766de Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 06:55:00 -0500 Subject: [PATCH 023/131] better tabstop calculation --- static/term.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/term.js b/static/term.js index 2962a539..4ebb42ef 100644 --- a/static/term.js +++ b/static/term.js @@ -2927,7 +2927,9 @@ Terminal.prototype.cursorForwardTab = function(params) { var row, param, line, ch; param = params[0] || 1; - param = param * 8; + param = (this.x + param * 8) & ~7; + param -= this.x; + row = this.y + this.ybase; line = this.lines[row]; ch = [this.defAttr, ' ']; @@ -2994,7 +2996,9 @@ Terminal.prototype.cursorBackwardTab = function(params) { var row, param, line, ch; param = params[0] || 1; - param = param * 8; + param = (this.x - param * 8) & ~7; + param = this.x - param; + row = this.y + this.ybase; line = this.lines[row]; ch = [this.defAttr, ' ']; From b5f7c3b40c69582ff079815994610806f1ba19f6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 07:38:33 -0500 Subject: [PATCH 024/131] more compliant tab stops --- static/term.js | 113 +++++++++++++++++++++++++++---------------------- 1 file changed, 63 insertions(+), 50 deletions(-) diff --git a/static/term.js b/static/term.js index 4ebb42ef..c387fb1b 100644 --- a/static/term.js +++ b/static/term.js @@ -73,7 +73,7 @@ function Terminal(cols, rows, handler) { this.wraparoundMode = false; this.mouseEvents; this.sendFocus; - this.tabs = []; + this.setupStops(); this.charset = null; this.normal = null; @@ -746,9 +746,7 @@ Terminal.prototype.scrollDisp = function(disp) { Terminal.prototype.write = function(data) { var l = data.length , i = 0 - , ch - , param - , row; + , ch; this.refreshStart = this.y; this.refreshEnd = this.y; @@ -802,11 +800,7 @@ Terminal.prototype.write = function(data) { // '\t' case '\t': - // should check tabstops - param = (this.x + 8) & ~7; - if (param <= this.cols) { - this.x = param; - } + this.x = this.nextStop(); break; // '\e' @@ -828,8 +822,7 @@ Terminal.prototype.write = function(data) { this.scroll(); } } - row = this.y + this.ybase; - this.lines[row][this.x] = [this.curAttr, ch]; + this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch]; this.x++; this.updateRange(this.y); } @@ -937,10 +930,9 @@ Terminal.prototype.write = function(data) { i++; break; - // ESC H Tab Set ( HTS is 0x88). + // ESC H Tab Set (HTS is 0x88). case 'H': - // this.tabSet(this.x); - this.state = normal; + this.tabSet(); break; // ESC = Application Keypad (DECPAM). @@ -1302,9 +1294,9 @@ Terminal.prototype.write = function(data) { break; // CSI Ps g Tab Clear (TBC). - // case 'g': - // this.tabClear(this.params); - // break; + case 'g': + this.tabClear(this.params); + break; // CSI Pm i Media Copy (MC). // CSI ? Pm i @@ -1808,6 +1800,7 @@ Terminal.prototype.resize = function(x, y) { } } } + this.setupStops(j); this.cols = x; // resize rows @@ -1864,6 +1857,37 @@ Terminal.prototype.maxRange = function() { this.refreshEnd = this.rows - 1; }; +Terminal.prototype.setupStops = function(i) { + if (i != null) { + if (!this.tabs[i]) { + i = this.prevStop(i); + } + } else { + this.tabs = {}; + i = 0; + } + + for (; i < this.cols; i += 8) { + this.tabs[i] = true; + } +}; + +Terminal.prototype.prevStop = function(x) { + if (x == null) x = this.x; + while (!this.tabs[--x] && x > 0); + return x >= this.cols + ? this.cols - 1 + : x < 0 ? 0 : x; +}; + +Terminal.prototype.nextStop = function(x) { + if (x == null) x = this.x; + while (!this.tabs[++x] && x < this.cols); + return x >= this.cols + ? this.cols - 1 + : x < 0 ? 0 : x; +}; + Terminal.prototype.eraseRight = function(x, y) { var line = this.lines[this.ybase + y] , ch = [this.curAttr, ' ']; // xterm @@ -1955,6 +1979,12 @@ Terminal.prototype.reset = function() { Terminal.call(this, this.cols, this.rows); }; +// ESC H Tab Set (HTS is 0x88). +Terminal.prototype.tabSet = function() { + this.tabs[this.x] = true; + this.state = normal; +}; + /** * CSI */ @@ -2726,7 +2756,8 @@ Terminal.prototype.setMode = function(params) { x: this.x, y: this.y, scrollTop: this.scrollTop, - scrollBottom: this.scrollBottom + scrollBottom: this.scrollBottom, + tabs: this.tabs }; this.reset(); this.normal = normal; @@ -2878,6 +2909,7 @@ Terminal.prototype.resetMode = function(params) { this.y = this.normal.y; this.scrollTop = this.normal.scrollTop; this.scrollBottom = this.normal.scrollBottom; + this.tabs = this.normal.tabs; this.normal = null; // if (params === 1049) { // this.x = this.savedX; @@ -2924,23 +2956,9 @@ Terminal.prototype.restoreCursor = function(params) { // CSI Ps I // Cursor Forward Tabulation Ps tab stops (default = 1) (CHT). Terminal.prototype.cursorForwardTab = function(params) { - var row, param, line, ch; - - param = params[0] || 1; - param = (this.x + param * 8) & ~7; - param -= this.x; - - row = this.y + this.ybase; - line = this.lines[row]; - ch = [this.defAttr, ' ']; - + var param = params[0] || 1; while (param--) { - line.splice(this.x++, 0, ch); - line.pop(); - if (this.x === this.cols) { - this.x--; - break; - } + this.x = this.nextStop(this.x); } }; @@ -2993,22 +3011,9 @@ Terminal.prototype.resetTitleModes = function(params) { // CSI Ps Z Cursor Backward Tabulation Ps tab stops (default = 1) (CBT). Terminal.prototype.cursorBackwardTab = function(params) { - var row, param, line, ch; - - param = params[0] || 1; - param = (this.x - param * 8) & ~7; - param = this.x - param; - - row = this.y + this.ybase; - line = this.lines[row]; - ch = [this.defAttr, ' ']; - + var param = params[0] || 1; while (param--) { - line.splice(--this.x, 1); - line.push(ch); - if (this.x === 0) { - break; - } + this.x = this.prevStop(this.x); } }; @@ -3024,8 +3029,16 @@ Terminal.prototype.repeatPrecedingCharacter = function(params) { // CSI Ps g Tab Clear (TBC). // Ps = 0 -> Clear Current Column (default). // Ps = 3 -> Clear All. +// Potentially: +// Ps = 2 -> Clear Stops on Line. +// http://vt100.net/annarbor/aaa-ug/section6.html Terminal.prototype.tabClear = function(params) { - ; + var param = params[0]; + if (param <= 0) { + delete this.tabs[this.x]; + } else if (param === 3) { + this.tabs = {}; + } }; // CSI Pm i Media Copy (MC). From e58ab0a5e9e7ceffaba14734c893ed0492b113af Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 09:08:58 -0500 Subject: [PATCH 025/131] implement 132 col mode. implement soft reset. refactor. --- static/term.js | 104 +++++++++++++++++++++++++++++++------------------ 1 file changed, 67 insertions(+), 37 deletions(-) diff --git a/static/term.js b/static/term.js index c387fb1b..1784a867 100644 --- a/static/term.js +++ b/static/term.js @@ -1275,11 +1275,13 @@ Terminal.prototype.write = function(data) { // this.resetTitleModes(this.params); // break; // } - // if (this.params.length > 1) { + // if (this.params.length > 2) { // this.initMouseTracking(this.params); // break; // } - this.scrollDown(this.params); + if (this.params.length < 2 && !this.prefix) { + this.scrollDown(this.params); + } break; // CSI Ps Z @@ -1506,10 +1508,7 @@ Terminal.prototype.write = function(data) { } this.updateRange(this.y); - - if (this.refreshEnd >= this.refreshStart) { - this.refresh(this.refreshStart, this.refreshEnd); - } + this.refresh(this.refreshStart, this.refreshEnd); }; Terminal.prototype.writeln = function(data) { @@ -2070,7 +2069,7 @@ Terminal.prototype.cursorPos = function(params) { // Ps = 2 -> Selective Erase All. Terminal.prototype.eraseInDisplay = function(params) { var j; - switch (params[0] || 0) { + switch (params[0]) { case 0: this.eraseRight(this.x, this.y); j = this.y + 1; @@ -2105,7 +2104,7 @@ Terminal.prototype.eraseInDisplay = function(params) { // Ps = 1 -> Selective Erase to Left. // Ps = 2 -> Selective Erase All. Terminal.prototype.eraseInLine = function(params) { - switch (params[0] || 0) { + switch (params[0]) { case 0: this.eraseRight(this.x, this.y); break; @@ -2181,16 +2180,11 @@ Terminal.prototype.eraseInLine = function(params) { // Ps = 4 8 ; 5 ; Ps -> Set background color to the second // Ps. Terminal.prototype.charAttributes = function(params) { - var i, l, p, bg, fg; - - if (params.length === 0) { - // default - this.curAttr = this.defAttr; - return; - } - - l = params.length; - i = 0; + var l = params.length + , i = 0 + , bg + , fg + , p; for (; i < l; i++) { p = params[i]; @@ -2284,11 +2278,27 @@ Terminal.prototype.charAttributes = function(params) { // CSI ? 5 3 n Locator available, if compiled-in, or // CSI ? 5 0 n No Locator, if not. Terminal.prototype.deviceStatus = function(params) { - if (this.prefix === '?') { + if (!this.prefix) { + switch (params[0]) { + case 5: + // status report + this.send('\x1b[0n'); + break; + case 6: + // cursor position + this.send('\x1b[' + + (this.y + 1) + + ';' + + (this.x + 1) + + 'R'); + break; + } + } else if (this.prefix === '?') { // modern xterm doesnt seem to // respond to any of these except ?6, 6, and 5 switch (params[0]) { case 6: + // cursor position this.send('\x1b[' + (this.y + 1) + ';' @@ -2304,6 +2314,7 @@ Terminal.prototype.deviceStatus = function(params) { // this.send('\x1b[?21n'); break; case 26: + // north american keyboard // this.send('\x1b[?27;1;0;0n'); break; case 53: @@ -2311,19 +2322,6 @@ Terminal.prototype.deviceStatus = function(params) { // this.send('\x1b[?50n'); break; } - return; - } - switch (params[0]) { - case 5: - this.send('\x1b[0n'); - break; - case 6: - this.send('\x1b[' - + (this.y + 1) - + ';' - + (this.x + 1) - + 'R'); - break; } }; @@ -2674,7 +2672,13 @@ Terminal.prototype.HVPosition = function(params) { // http://vt100.net/docs/vt220-rm/chapter4.html Terminal.prototype.setMode = function(params) { if (typeof params === 'object') { - while (params.length) this.setMode(params.shift()); + var l = params.length + , i = 0; + + for (; i < l; i++) { + this.setMode(params[i]); + } + return; } @@ -2692,6 +2696,10 @@ Terminal.prototype.setMode = function(params) { case 1: this.applicationKeypad = true; break; + case 3: // 132 col mode + this.savedCols = this.cols; + this.resize(132, this.rows); + break; case 6: this.originMode = true; break; @@ -2850,7 +2858,13 @@ Terminal.prototype.setMode = function(params) { // Ps = 2 0 0 4 -> Reset bracketed paste mode. Terminal.prototype.resetMode = function(params) { if (typeof params === 'object') { - while (params.length) this.resetMode(params.shift()); + var l = params.length + , i = 0; + + for (; i < l; i++) { + this.resetMode(params[i]); + } + return; } @@ -2868,6 +2882,12 @@ Terminal.prototype.resetMode = function(params) { case 1: this.applicationKeypad = false; break; + case 3: + if (this.cols === 132 && this.savedCols) { + this.resize(this.savedCols, this.rows); + } + delete this.savedCols; + break; case 6: this.originMode = false; break; @@ -2958,7 +2978,7 @@ Terminal.prototype.restoreCursor = function(params) { Terminal.prototype.cursorForwardTab = function(params) { var param = params[0] || 1; while (param--) { - this.x = this.nextStop(this.x); + this.x = this.nextStop(); } }; @@ -3013,7 +3033,7 @@ Terminal.prototype.resetTitleModes = function(params) { Terminal.prototype.cursorBackwardTab = function(params) { var param = params[0] || 1; while (param--) { - this.x = this.prevStop(this.x); + this.x = this.prevStop(); } }; @@ -3102,8 +3122,18 @@ Terminal.prototype.setPointerMode = function(params) { }; // CSI ! p Soft terminal reset (DECSTR). +// http://vt100.net/docs/vt220-rm/table4-10.html Terminal.prototype.softReset = function(params) { - this.reset(); + this.cursorHidden = false; + this.insertMode = false; + this.originMode = false; + this.wraparoundMode = false; // autowrap + this.applicationKeypad = false; // ? + this.scrollTop = 0; + this.scrollBottom = this.rows - 1; + this.curAttr = this.defAttr; + this.x = this.y = 0; // ? + this.charset = null; }; // CSI Ps$ p From 55e086e54a10912a25f63e3a0bf4d22ce052c0a4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 11:06:13 -0500 Subject: [PATCH 026/131] revise DEC SCLD charset. --- static/term.js | 32 ++++++++++++++++++++------------ 1 file changed, 20 insertions(+), 12 deletions(-) diff --git a/static/term.js b/static/term.js index 1784a867..beb7aeab 100644 --- a/static/term.js +++ b/static/term.js @@ -764,8 +764,8 @@ Terminal.prototype.write = function(data) { case normal: switch (ch) { // '\0' - case '\0': - break; + // case '\0': + // break; // '\a' case '\x07': @@ -803,6 +803,14 @@ Terminal.prototype.write = function(data) { this.x = this.nextStop(); break; + // shift out + // case '\x0e': + // break; + + // shift in + // case '\x0f': + // break; + // '\e' case '\x1b': this.state = escaped; @@ -3569,21 +3577,21 @@ Terminal.prototype.deleteColumns = function() { // http://vt100.net/docs/vt102-ug/table5-13.html // A lot of curses apps use this if they see TERM=xterm. // testing: echo -e '\e(0a\e(B' -// The real xterm output seems to conflict with the -// reference above. The table below uses -// the exact same charset xterm outputs. +// The xterm output sometimes seems to conflict with the +// reference above. xterm seems in line with the reference +// when running vttest however. +// The table below now uses xterm's output from vttest. var SCLD = { - '_': '\u005f', // '_' - blank ? should this be ' ' ? '`': '\u25c6', // '◆' 'a': '\u2592', // '▒' - 'b': '\u0062', // 'b' - should this be: '\t' ? - 'c': '\u0063', // 'c' - should this be: '\f' ? - 'd': '\u0064', // 'd' - should this be: '\r' ? - 'e': '\u0065', // 'e' - should this be: '\n' ? + 'b': '\u0009', // '\t' + 'c': '\u000c', // '\f' + 'd': '\u000d', // '\r' + 'e': '\u000a', // '\n' 'f': '\u00b0', // '°' 'g': '\u00b1', // '±' - 'h': '\u2592', // '▒' - NL ? should this be '\n' ? - 'i': '\u2603', // '☃' - VT ? should this be '\v' ? + 'h': '\u2424', // '\u2424' (NL) + 'i': '\u000b', // '\v' 'j': '\u2518', // '┘' 'k': '\u2510', // '┐' 'l': '\u250c', // '┌' From 7a7bcc89bce54ef0fc3908892ca1ac207fd27c3c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 11:54:09 -0500 Subject: [PATCH 027/131] misc --- lib/config.js | 2 +- package.json | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/config.js b/lib/config.js index 1b151d9a..ca33fa37 100644 --- a/lib/config.js +++ b/lib/config.js @@ -213,7 +213,7 @@ function parseArg() { var getarg = function() { var arg = argv.shift(); - if (arg && arg.indexOf('--') === 0) { + if (arg && arg.indexOf('-') === 0) { arg = arg.split('='); if (arg.length > 1) { argv.unshift(arg.slice(1).join('=')); diff --git a/package.json b/package.json index 4d110f27..555790b3 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "description": "A terminal for your browser", "author": "Christopher Jeffrey", "version": "0.2.5", - "main": "./lib/tty.js", + "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", "preferGlobal": false, @@ -15,6 +15,6 @@ "dependencies": { "express": ">= 2.5.8", "socket.io": ">= 0.8.7", - "pty.js": ">= 0.0.7" + "pty.js": ">= 0.0.8" } } From 5cf6dc2ad6fcc0923b81391d631d5c74b12bc173 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 6 Apr 2012 13:01:47 -0500 Subject: [PATCH 028/131] remove unnecessary updateRange call. remove Terminal._colors. --- static/term.js | 22 ++++++++++------------ 1 file changed, 10 insertions(+), 12 deletions(-) diff --git a/static/term.js b/static/term.js index beb7aeab..0893090d 100644 --- a/static/term.js +++ b/static/term.js @@ -189,9 +189,6 @@ Terminal.defaultColors = { Terminal.colors[256] = Terminal.defaultColors.bg; Terminal.colors[257] = Terminal.defaultColors.fg; -// save fallback -Terminal._colors = Terminal.colors.slice(); - Terminal.termName = 'xterm'; Terminal.geometry = [80, 30]; Terminal.cursorBlink = true; @@ -613,15 +610,13 @@ Terminal.prototype.refresh = function(start, end) { if (bgColor !== 256) { out += 'background-color:' - + (Terminal.colors[bgColor] - || Terminal._colors[bgColor]) + + Terminal.colors[bgColor] + ';'; } if (fgColor !== 257) { out += 'color:' - + (Terminal.colors[fgColor] - || Terminal._colors[fgColor]) + + Terminal.colors[fgColor] + ';'; } @@ -728,7 +723,9 @@ Terminal.prototype.scroll = function() { this.lines.splice(this.ybase + this.scrollTop, 1); } - this.maxRange(); + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); }; Terminal.prototype.scrollDisp = function(disp) { @@ -1508,9 +1505,6 @@ Terminal.prototype.write = function(data) { this.prefix = ''; this.postfix = ''; - - // ensure any line changes update - this.updateRange(this.y); break; } } @@ -1976,7 +1970,9 @@ Terminal.prototype.reverseIndex = function() { this.lines.splice(this.y + this.ybase, 0, this.blankLine(true)); j = this.rows - 1 - this.scrollBottom; this.lines.splice(this.rows - 1 + this.ybase - j + 1, 1); - this.maxRange(); + // this.maxRange(); + this.updateRange(this.scrollTop); + this.updateRange(this.scrollBottom); } this.state = normal; }; @@ -2407,6 +2403,7 @@ Terminal.prototype.insertLines = function(params) { } // this.maxRange(); + this.updateRange(this.y); this.updateRange(this.scrollBottom); }; @@ -2430,6 +2427,7 @@ Terminal.prototype.deleteLines = function(params) { } // this.maxRange(); + this.updateRange(this.y); this.updateRange(this.scrollBottom); }; From 012e7c36a4e21d5e6c0ef70851f985401a18eea7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 7 Apr 2012 11:34:15 -0500 Subject: [PATCH 029/131] small changes --- lib/config.js | 13 ++++++++----- static/term.js | 24 +++++++++++++++++++----- 2 files changed, 27 insertions(+), 10 deletions(-) diff --git a/lib/config.js b/lib/config.js index ca33fa37..d543d9f7 100644 --- a/lib/config.js +++ b/lib/config.js @@ -28,7 +28,9 @@ var schema = { // cursorBlink: true, // scrollback: 1000, // screenKeys: false, - // colors: [] + // colors: [], + // programFeatures: false, + // debug: false } }; @@ -211,16 +213,17 @@ function parseArg() { , opt = {} , arg; - var getarg = function() { + function getarg() { var arg = argv.shift(); - if (arg && arg.indexOf('-') === 0) { + if (arg && arg.indexOf('--') === 0) { arg = arg.split('='); if (arg.length > 1) { argv.unshift(arg.slice(1).join('=')); } + return arg[0]; } - return arg[0]; - }; + return arg; + } while (argv.length) { arg = getarg(); diff --git a/static/term.js b/static/term.js index 0893090d..fb3b7920 100644 --- a/static/term.js +++ b/static/term.js @@ -34,7 +34,8 @@ * Shared */ -var window = this; +var window = this + , document = this.document; /** * States @@ -210,10 +211,10 @@ Terminal.prototype.focus = function() { if (Terminal.focus) { Terminal.focus.cursorState = 0; Terminal.focus.refresh(Terminal.focus.y, Terminal.focus.y); - if (Terminal.focus.sendFocus) Terminal.focus.send('\x1b[>O'); + if (Terminal.focus.sendFocus) Terminal.focus.send('\x1b[O'); } Terminal.focus = this; - if (this.sendFocus) this.send('\x1b[>I'); + if (this.sendFocus) this.send('\x1b[I'); this.showCursor(); }; @@ -476,6 +477,10 @@ Terminal.prototype.bindMouse = function() { x += 32; y += 32; + // Can't go above 95 + 32 (127) without utf8 + if (x > 127) x = 127; + if (y > 127) y = 127; + return { x: x, y: y }; } @@ -1980,6 +1985,7 @@ Terminal.prototype.reverseIndex = function() { // ESC c Full Reset (RIS). Terminal.prototype.reset = function() { Terminal.call(this, this.cols, this.rows); + this.refresh(0, this.rows - 1); }; // ESC H Tab Set (HTS is 0x88). @@ -3656,6 +3662,14 @@ var setInterval = this.setInterval; * Expose */ -this.Terminal = Terminal; +if (typeof module !== 'undefined') { + module.exports = Terminal; +} else { + this.Terminal = Terminal; +} -}).call(this); +}).call(function() { + return typeof window !== 'undefined' + ? this || window + : global; +}()); From e6e05d8f1dc57541814fc3c3362b2fa291d1fb4a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 8 Apr 2012 21:25:30 -0500 Subject: [PATCH 030/131] add dcs control sequences, ignore apc and pm. --- static/term.js | 113 ++++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 103 insertions(+), 10 deletions(-) diff --git a/static/term.js b/static/term.js index fb3b7920..057a6943 100644 --- a/static/term.js +++ b/static/term.js @@ -45,7 +45,9 @@ var normal = 0 , escaped = 1 , csi = 2 , osc = 3 - , charset = 4; + , charset = 4 + , dcs = 5 + , ignore = 6; /** * Terminal @@ -83,6 +85,8 @@ function Terminal(cols, rows, handler) { this.params = []; this.currentParam = 0; + this.prefix = ''; + this.postfix = ''; this.lines = []; var i = this.rows; @@ -857,23 +861,19 @@ Terminal.prototype.write = function(data) { // ESC P Device Control String ( DCS is 0x90). case 'P': - this.params = [-1]; + this.params = []; this.currentParam = 0; - this.state = osc; + this.state = dcs; break; // ESC _ Application Program Command ( APC is 0x9f). case '_': - this.params = [-1]; - this.currentParam = 0; - this.state = osc; + this.state = ignore; break; // ESC ^ Privacy Message ( PM is 0x9e). case '^': - this.params = [-1]; - this.currentParam = 0; - this.state = osc; + this.state = ignore; break; // ESC c Full Reset (RIS). @@ -1504,13 +1504,106 @@ Terminal.prototype.write = function(data) { // break; default: - this.error('Unknown CSI code: %s', ch, this.params); + this.error('Unknown CSI code: %s.', ch); break; } this.prefix = ''; this.postfix = ''; break; + + case dcs: + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; + + switch (this.prefix) { + // User-Defined Keys (DECUDK). + case '': + break; + + // Request Status String (DECRQSS). + // test: echo -e '\eP$q"p\e\\' + case '$q': + var pt = this.currentParam + , valid = false; + + switch (pt) { + // DECSCA + case '"q': + pt = '0"q'; + break; + + // DECSCL + case '"p': + pt = '61"p'; + break; + + // DECSTBM + case 'r': + pt = '' + + (this.scrollTop + 1) + + ';' + + (this.scrollBottom + 1) + + 'r'; + break; + + // SGR + case 'm': + pt = '0m'; + break; + + default: + this.error('Unknown DCS Pt: %s.', pt); + pt = ''; + break; + } + + this.send('\x1bP' + +valid + '$r' + pt + '\x1b\\'); + break; + + // Set Termcap/Terminfo Data (xterm, experimental). + case '+p': + break; + + // Request Termcap/Terminfo String (xterm, experimental) + // Regular xterm does not even respond to this sequence. + // This can cause a small glitch in vim. + // test: echo -ne '\eP+q6b64\e\\' + case '+q': + var pt = this.currentParam + , valid = false; + + this.send('\x1bP' + +valid + '+r' + pt + '\x1b\\'); + break; + + default: + this.error('Unknown DCS prefix: %s.', this.prefix); + break; + } + + this.currentParam = 0; + this.prefix = ''; + this.state = normal; + } else if (!this.currentParam) { + if (!this.prefix && ch !== '$' && ch !== '+') { + this.currentParam = ch; + } else if (this.prefix.length === 2) { + this.currentParam = ch; + } else { + this.prefix += ch; + } + } else { + this.currentParam += ch; + } + break; + + case ignore: + // For PM and APC. + if (ch === '\x1b' || ch === '\x07') { + if (ch === '\x1b') i++; + this.state = normal; + } + break; } } From 72602ff4b5e64f0c3e73edcb9d36f135c6901b74 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 8 Apr 2012 23:57:19 -0500 Subject: [PATCH 031/131] improve mouse events. add support for urxvt, vt200, sgr events. --- static/term.js | 179 ++++++++++++++++++++++++++++++++++++++++--------- 1 file changed, 146 insertions(+), 33 deletions(-) diff --git a/static/term.js b/static/term.js index 057a6943..18aa6098 100644 --- a/static/term.js +++ b/static/term.js @@ -384,10 +384,101 @@ Terminal.prototype.bindMouse = function() { sendEvent(button, pos); } + // encode button and + // position to characters + function encode(data, ch) { + if (!self.utfMouse) { + if (ch === 255) return data.push(0); + if (ch > 127) ch = 127; + data.push(ch); + } else { + if (ch === 2047) return data.push(0); + if (ch < 127) { + data.push(ch); + } else { + if (ch > 2047) ch = 2047; + data.push(0xC0 | (ch >> 6)); + data.push(0x80 | (ch & 0x3F)); + } + } + } + // send a mouse event: - // ^[[M Cb Cx Cy + // regular/utf8: ^[[M Cb Cx Cy + // urxvt: ^[[ Cb ; Cx ; Cy M + // sgr: ^[[ Cb ; Cx ; Cy M/m + // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r + // locator: CSI P e ; P b ; P r ; P c ; P p & w function sendEvent(button, pos) { - self.send('\x1b[M' + String.fromCharCode(button, pos.x, pos.y)); + if (self.vt300Mouse) { + // NOTE: Unstable. + // http://www.vt100.net/docs/vt3xx-gp/chapter15.html + button &= 3; + pos.x -= 32; + pos.y -= 32; + var data = '\x1b[24'; + if (button === 0) data += '1'; + else if (button === 1) data += '3'; + else if (button === 2) data += '5'; + else if (button === 3) return; + else data += '0'; + data += '~[' + pos.x + ',' + pos.y + ']\r'; + self.send(data); + return; + } + + if (self.decLocator) { + // NOTE: Unstable. + button &= 3; + pos.x -= 32; + pos.y -= 32; + if (button === 0) button = 2; + else if (button === 1) button = 4; + else if (button === 2) button = 6; + else if (button === 3) button = 3; + self.send('\x1b[' + + button + + ';' + + (button === 3 ? 4 : 0) + + ';' + + pos.y + + ';' + + pos.x + + ';' + + (pos.page || 0) + + '&w'); + return; + } + + if (self.urxvtMouse) { + pos.x -= 32; + pos.y -= 32; + pos.x++; + pos.y++; + self.send('\x1b[' + button + ';' + pos.x + ';' + pos.y + 'M'); + return; + } + + if (self.sgrMouse) { + pos.x -= 32; + pos.y -= 32; + self.send('\x1b[<' + + ((button & 3) === 3 ? button & ~3 : button) + + ';' + + pos.x + + ';' + + pos.y + + ((button & 3) === 3 ? 'm' : 'M')); + return; + } + + var data = []; + + encode(data, button); + encode(data, pos.x); + encode(data, pos.y); + + self.send('\x1b[M' + String.fromCharCode.apply(String, data)); } function getButton(ev) { @@ -438,6 +529,14 @@ Terminal.prototype.bindMouse = function() { ctrl = ev.ctrlKey ? 16 : 0; mod = shift | meta | ctrl; + // no mods + if (self.vt200Mouse) { + // ctrl only + mod &= ctrl; + } else if (self.x10Mouse || self.vt300Mouse || self.decLocator) { + mod = 0; + } + // increment to SP button = (32 + (mod << 2)) + button; @@ -481,10 +580,6 @@ Terminal.prototype.bindMouse = function() { x += 32; y += 32; - // Can't go above 95 + 32 (127) without utf8 - if (x > 127) x = 127; - if (y > 127) y = 127; - return { x: x, y: y }; } @@ -498,19 +593,31 @@ Terminal.prototype.bindMouse = function() { self.focus(); // bind events - on(document, 'mousemove', sendMove); - on(document, 'mouseup', function up(ev) { - sendButton(ev); - off(document, 'mousemove', sendMove); - off(document, 'mouseup', up); - return cancel(ev); - }); + if (!self.x10Mouse + && !self.vt200Mouse + && !self.vt300Mouse + && !self.decLocator) { + on(document, 'mousemove', sendMove); + } + + // x10 compatibility mode can't send button releases + if (!self.x10Mouse) { + on(document, 'mouseup', function up(ev) { + sendButton(ev); + off(document, 'mousemove', sendMove); + off(document, 'mouseup', up); + return cancel(ev); + }); + } return cancel(ev); }); on(el, wheelEvent, function(ev) { if (!self.mouseEvents) return; + if (self.x10Mouse + || self.vt300Mouse + || self.decLocator) return; sendButton(ev); return cancel(ev); }); @@ -2811,26 +2918,23 @@ Terminal.prototype.setMode = function(params) { case 7: this.wraparoundMode = true; break; - case 9: // X10 Mouse - // button press only. + case 12: + // this.cursorBlink = true; break; + case 9: // X10 Mouse + // no release, no motion, no wheel, no modifiers. case 1000: // vt200 mouse - // no wheel events, no motion. - // no modifiers except control. - // button press, release. - break; - case 1001: // vt200 highlight mouse - // no wheel events, no motion. - // first event is to send tracking instead - // of button press, *then* button release. - break; + // no motion. + // no modifiers, except control on the wheel. case 1002: // button event mouse case 1003: // any event mouse - // button press, release, wheel, and motion. - // no modifiers except control. - this.log('Binding to mouse events.'); + // any event - sends motion events, + // even if there is no button held down. + this.x10Mouse = params === 9; + this.vt200Mouse = params === 1000; this.mouseEvents = true; this.element.style.cursor = 'default'; + this.log('Binding to mouse events.'); break; case 1004: // send focusin/focusout events // focusin: ^[[>I @@ -2838,16 +2942,19 @@ Terminal.prototype.setMode = function(params) { this.sendFocus = true; break; case 1005: // utf8 ext mode mouse + this.utfMouse = true; // for wide terminals // simply encodes large values as utf8 characters break; case 1006: // sgr ext mode mouse + this.sgrMouse = true; // for wide terminals // does not add 32 to fields // press: ^[[ Ps; Ps T @@ -3531,7 +3642,9 @@ Terminal.prototype.fillRectangle = function(params) { // Pu = 1 <- device physical pixels. // Pu = 2 <- character cells. Terminal.prototype.enableLocatorReporting = function(params) { - ; + var val = params[0] > 0; + //this.mouseEvents = val; + //this.decLocator = val; }; // CSI Pt; Pl; Pb; Pr$ z From a90547f3895234bc2080bcbdb0572739a7f59e48 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 9 Apr 2012 13:47:07 -0500 Subject: [PATCH 032/131] rewrite bin/tty.js. daemonize from node instead of bash script. --- bin/tty.js | 35 ++-------------------------- lib/config.js | 63 +++++++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 65 insertions(+), 33 deletions(-) diff --git a/bin/tty.js b/bin/tty.js index d824a74b..ade33508 100755 --- a/bin/tty.js +++ b/bin/tty.js @@ -1,34 +1,3 @@ -#!/bin/bash +#!/usr/bin/env node -f=$0 -while test -L "$f"; do - f=$(readlink "$f") -done -dir="$(dirname "$f")/.." - -node=$(which node 2>/dev/null) -if test -z "$node"; then - node="/usr/local/bin/node" - if test ! -f "$node"; then - echo "Node not found." - exit 1 - fi -fi - -for arg in "$@"; do - case "$arg" in - -d | --daemonize | production | --production) - daemonize=1 - break - ;; - -h | --help) - exec man "$dir/man/tty.js.1" - ;; - esac -done - -if test -n "$daemonize"; then - (setsid "$node" "$dir/index.js" $@ > /dev/null 2>&1 &) -else - exec "$node" "$dir/index.js" $@ -fi +require('../'); diff --git a/lib/config.js b/lib/config.js index d543d9f7..abcfad88 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,3 +1,8 @@ +/** + * tty.js: config.js + * Copyright (c) 2012, Christopher Jeffrey (MIT License) + */ + var path = require('path') , fs = require('fs'); @@ -203,6 +208,61 @@ function checkLegacy(conf) { } } +/** + * Daemonize + */ + +function daemonize() { + var argv = process.argv.slice(); + + if (~argv.indexOf('--daemonized')) { + return; + } + + var spawn = require('child_process').spawn + , code; + + argv.push('--daemonized'); + + // escape arguments + argv = argv.map(function(arg) { + arg = arg.replace(/(["$\\])/g, '\\$1'); + return '"' + arg + '"'; + }).join(' '); + + // fork with sh + code = '(setsid ' + argv + ' > /dev/null 2>&1 &)'; + spawn('/bin/sh', [ '-c', code ]).on('exit', function(code) { + process.exit(code || 0); + }); + + // kill current stack + process.once('uncaughtException', function() {}); + throw 'stop'; +} + +/** + * Help + */ + +function help() { + var spawn = require('child_process').spawn; + + var options = { + cwd: process.cwd(), + env: process.env, + setsid: false, + customFds: [0, 1, 2] + }; + + spawn('man', + [__dirname + '/../man/tty.js.1'], + options); + + // kill current stack + process.once('uncaughtException', function() {}); + throw 'stop'; +} /** * Parse Arguments @@ -240,10 +300,13 @@ function parseArg() { break; case '-h': case '--help': + help(); + break; case 'production': case '--production': case '-d': case '--daemonize': + daemonize(); break; default: break; From 02e5dd58368b5ef225ca081a6d7d5b7d906b9d49 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 9 Apr 2012 22:57:14 -0500 Subject: [PATCH 033/131] fix strange vt200 mouse bug with vim --- static/term.js | 35 +++++++++++++++++++++++++---------- 1 file changed, 25 insertions(+), 10 deletions(-) diff --git a/static/term.js b/static/term.js index 18aa6098..41077c94 100644 --- a/static/term.js +++ b/static/term.js @@ -580,7 +580,16 @@ Terminal.prototype.bindMouse = function() { x += 32; y += 32; - return { x: x, y: y }; + return { + x: x, + y: y, + down: ev.type === 'mousedown', + up: ev.type === 'mouseup', + wheel: ev.type === wheelEvent, + move: ev.type === 'mousemove' + //button: getButton(ev), + //realButton: getButton(ev) & 3 + }; } on(el, 'mousedown', function(ev) { @@ -592,19 +601,25 @@ Terminal.prototype.bindMouse = function() { // ensure focus self.focus(); - // bind events - if (!self.x10Mouse - && !self.vt200Mouse - && !self.vt300Mouse - && !self.decLocator) { - on(document, 'mousemove', sendMove); + // fix for odd bug + if (self.vt200Mouse) { + sendButton({ __proto__: ev, type: 'mouseup' }); + return cancel(ev); } + // bind events + var motion = !self.x10Mouse + && !self.vt200Mouse + && !self.vt300Mouse + && !self.decLocator; + + if (motion) on(document, 'mousemove', sendMove); + // x10 compatibility mode can't send button releases if (!self.x10Mouse) { on(document, 'mouseup', function up(ev) { sendButton(ev); - off(document, 'mousemove', sendMove); + if (motion) off(document, 'mousemove', sendMove); off(document, 'mouseup', up); return cancel(ev); }); @@ -2937,8 +2952,8 @@ Terminal.prototype.setMode = function(params) { this.log('Binding to mouse events.'); break; case 1004: // send focusin/focusout events - // focusin: ^[[>I - // focusout: ^[[>O + // focusin: ^[[I + // focusout: ^[[O this.sendFocus = true; break; case 1005: // utf8 ext mode mouse From 752e57702d791fab3a23df6b6fe9b405d26ff798 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 11 Apr 2012 23:04:05 -0500 Subject: [PATCH 034/131] improve daemonizer --- lib/config.js | 16 ++++------------ static/term.js | 4 +--- 2 files changed, 5 insertions(+), 15 deletions(-) diff --git a/lib/config.js b/lib/config.js index abcfad88..ecc7ed38 100644 --- a/lib/config.js +++ b/lib/config.js @@ -213,30 +213,22 @@ function checkLegacy(conf) { */ function daemonize() { - var argv = process.argv.slice(); + if (process.env.IS_DAEMONIC) return; - if (~argv.indexOf('--daemonized')) { - return; - } - - var spawn = require('child_process').spawn + var argv = process.argv.slice() + , spawn = require('child_process').spawn , code; - argv.push('--daemonized'); - - // escape arguments argv = argv.map(function(arg) { arg = arg.replace(/(["$\\])/g, '\\$1'); return '"' + arg + '"'; }).join(' '); - // fork with sh - code = '(setsid ' + argv + ' > /dev/null 2>&1 &)'; + code = '(IS_DAEMONIC=1 setsid ' + argv + ' > /dev/null 2>& 1 &)'; spawn('/bin/sh', [ '-c', code ]).on('exit', function(code) { process.exit(code || 0); }); - // kill current stack process.once('uncaughtException', function() {}); throw 'stop'; } diff --git a/static/term.js b/static/term.js index 41077c94..a3f7f92b 100644 --- a/static/term.js +++ b/static/term.js @@ -3890,7 +3890,5 @@ if (typeof module !== 'undefined') { } }).call(function() { - return typeof window !== 'undefined' - ? this || window - : global; + return this || (typeof window !== 'undefined' ? window : global); }()); From b760b3e2c41198879fb8bd208b6367f60a59705a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 12 Apr 2012 02:25:20 -0500 Subject: [PATCH 035/131] v0.2.6 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 555790b3..d0240478 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.5", + "version": "0.2.6", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From 6a445642c86fa41a5148f1b4c6d6374ebc5a5ce3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 12 Apr 2012 02:42:17 -0500 Subject: [PATCH 036/131] static/tty.js wrapper --- static/tty.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/static/tty.js b/static/tty.js index b5c8c400..9e566375 100644 --- a/static/tty.js +++ b/static/tty.js @@ -794,4 +794,6 @@ this.tty = { Terminal: Terminal }; -}).call(this); +}).call(function() { + return this || (typeof window !== 'undefined' ? window : global); +}()); From 4761db233a2e3792064373b988543d97c0b86be6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 13 Apr 2012 18:52:24 -0500 Subject: [PATCH 037/131] pass socket reference to Tab and Window --- static/tty.js | 23 ++++++++++++----------- 1 file changed, 12 insertions(+), 11 deletions(-) diff --git a/static/tty.js b/static/tty.js index 9e566375..2599974a 100644 --- a/static/tty.js +++ b/static/tty.js @@ -47,7 +47,7 @@ function open() { if (open) { on(open, 'click', function() { - new Window; + new Window(socket); }); } @@ -61,7 +61,7 @@ function open() { socket.on('connect', function() { reset(); - new Window; + new Window(socket); }); socket.on('data', function(id, data) { @@ -115,7 +115,7 @@ function reset() { * Window */ -function Window() { +function Window(socket) { var self = this; var el @@ -142,6 +142,7 @@ function Window() { title.className = 'title'; title.innerHTML = ''; + this.socket = socket; this.element = el; this.grip = grip; this.bar = bar; @@ -392,7 +393,7 @@ Window.prototype.each = function(func) { }; Window.prototype.createTab = function() { - new Tab(this); + new Tab(this, this.socket); }; Window.prototype.highlight = function() { @@ -432,7 +433,7 @@ Window.prototype.previousTab = function() { * Tab */ -function Tab(win) { +function Tab(win, socket) { var self = this; var id = uid++ @@ -457,6 +458,7 @@ function Tab(win) { }); this.id = id; + this.socket = socket; this.window = win; this.button = button; this.element = null; @@ -466,7 +468,7 @@ function Tab(win) { win.tabs.push(this); terms[id] = this; - socket.emit('create', cols, rows, function(err, data) { + this.socket.emit('create', cols, rows, function(err, data) { if (err) return self._destroy(); self.pty = data.pty; self.setProcessName(data.process); @@ -476,7 +478,7 @@ function Tab(win) { inherits(Tab, Terminal); Tab.prototype.handler = function(data) { - socket.emit('data', this.id, data); + this.socket.emit('data', this.id, data); }; Tab.prototype.handleTitle = function(title) { @@ -537,7 +539,7 @@ Tab.prototype.focus = function() { Tab.prototype._resize = Tab.prototype.resize; Tab.prototype.resize = function(cols, rows) { - socket.emit('resize', this.id, cols, rows); + this.socket.emit('resize', this.id, cols, rows); this._resize(cols, rows); }; @@ -571,7 +573,7 @@ Tab.prototype._destroy = function() { Tab.prototype.destroy = function() { if (this.destroyed) return; - socket.emit('kill', this.id); + this.socket.emit('kill', this.id); this._destroy(); }; @@ -681,7 +683,6 @@ Tab.prototype.bindMouse = function() { lynx: true }; - // Mouse support for Irssi. on(self.element, wheelEvent, function(ev) { if (self.mouseEvents) return; if (!programs[self.process]) return; @@ -703,7 +704,7 @@ Tab.prototype.bindMouse = function() { Tab.prototype.pollProcessName = function(func) { var self = this; - socket.emit('process', this.id, function(err, name) { + this.socket.emit('process', this.id, function(err, name) { if (!err) self.setProcessName(name); if (func) func(err, name); }); From ed07ffa21505f16dd0ad2857bc688ef8def6128c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 16 Apr 2012 17:10:05 -0500 Subject: [PATCH 038/131] refactor doc name. update document title. --- static/tty.js | 39 ++++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 19 deletions(-) diff --git a/static/tty.js b/static/tty.js index 2599974a..3bb2a931 100644 --- a/static/tty.js +++ b/static/tty.js @@ -9,13 +9,13 @@ * Elements */ -var doc = this.document +var document = this.document , window = this , root , body , h1; -var initialTitle = doc.title; +var initialTitle = document.title; /** * Shared @@ -33,17 +33,17 @@ var socket function open() { if (socket) return; - root = doc.documentElement; - body = doc.body; - h1 = doc.getElementsByTagName('h1')[0]; + root = document.documentElement; + body = document.body; + h1 = document.getElementsByTagName('h1')[0]; socket = io.connect(); windows = []; terms = {}; uid = 0; - var open = doc.getElementById('open') - , lights = doc.getElementById('lights'); + var open = document.getElementById('open') + , lights = document.getElementById('lights'); if (open) { on(open, 'click', function() { @@ -262,12 +262,12 @@ Window.prototype.drag = function(ev) { el.style.cursor = ''; root.style.cursor = ''; - off(doc, 'mousemove', move); - off(doc, 'mouseup', up); + off(document, 'mousemove', move); + off(document, 'mouseup', up); } - on(doc, 'mousemove', move); - on(doc, 'mouseup', up); + on(document, 'mousemove', move); + on(document, 'mouseup', up); }; Window.prototype.resizing = function(ev) { @@ -316,12 +316,12 @@ Window.prototype.resizing = function(ev) { root.style.cursor = ''; term.element.style.height = ''; - off(doc, 'mousemove', move); - off(doc, 'mouseup', up); + off(document, 'mousemove', move); + off(document, 'mouseup', up); } - on(doc, 'mousemove', move); - on(doc, 'mouseup', up); + on(document, 'mousemove', move); + on(document, 'mouseup', up); }; Window.prototype.maximize = function() { @@ -525,6 +525,7 @@ Tab.prototype.focus = function() { win.focused = this; win.title.innerHTML = this.process; + document.title = this.title || initialTitle; this.button.style.fontWeight = 'bold'; this.button.style.color = ''; } @@ -776,13 +777,13 @@ function sanitize(text) { function load() { if (socket) return; - off(doc, 'load', load); - off(doc, 'DOMContentLoaded', load); + off(document, 'load', load); + off(document, 'DOMContentLoaded', load); open(); } -on(doc, 'load', load); -on(doc, 'DOMContentLoaded', load); +on(document, 'load', load); +on(document, 'DOMContentLoaded', load); setTimeout(load, 200); /** From cd6ddb313fddfe66b80eef2d6fdcf7d8e93970c5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 16 Apr 2012 17:23:57 -0500 Subject: [PATCH 039/131] sync terminal id with server --- lib/tty.js | 11 ++++++----- static/tty.js | 35 +++++++++++++++++++++-------------- 2 files changed, 27 insertions(+), 19 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index a51855bb..55906713 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -132,8 +132,7 @@ io.set('authorization', function(data, next) { io.sockets.on('connection', function(socket) { var req = socket.handshake - , terms = {} - , uid = 0; + , terms = {}; // Kill older session. if (conf.sessions && conf.users) { @@ -148,9 +147,9 @@ io.sockets.on('connection', function(socket) { } socket.on('create', function(cols, rows, func) { - var id = uid++ - , len = Object.keys(terms).length - , term; + var len = Object.keys(terms).length + , term + , id; if (len >= conf.limitPerUser || pty.total >= conf.limitGlobal) { return func('Terminal limit.'); @@ -163,6 +162,7 @@ io.sockets.on('connection', function(socket) { cwd: conf.cwd || process.env.HOME }); + id = term.pty; terms[id] = term; term.on('data', function(data) { @@ -188,6 +188,7 @@ io.sockets.on('connection', function(socket) { term.pty, term.fd, term.pid); return func(null, { + id: id, pty: term.pty, process: sanitize(conf.shell) }); diff --git a/static/tty.js b/static/tty.js index 3bb2a931..aafa6b16 100644 --- a/static/tty.js +++ b/static/tty.js @@ -21,26 +21,27 @@ var initialTitle = document.title; * Shared */ -var socket - , windows - , terms - , uid; +var windows + , terms; /** * Open */ function open() { - if (socket) return; + var socket = io.connect() + , tty = window.tty; root = document.documentElement; body = document.body; h1 = document.getElementsByTagName('h1')[0]; - socket = io.connect(); windows = []; terms = {}; - uid = 0; + + tty.socket = socket; + tty.windows = windows; + tty.terms = terms; var open = document.getElementById('open') , lights = document.getElementById('lights'); @@ -65,6 +66,7 @@ function open() { }); socket.on('data', function(id, data) { + if (!terms[id]) return; terms[id].write(data); }); @@ -106,9 +108,12 @@ function reset() { while (i--) { windows[i].destroy(); } + windows = []; terms = {}; - uid = 0; + + window.tty.windows = windows; + window.tty.terms = terms; } /** @@ -436,8 +441,7 @@ Window.prototype.previousTab = function() { function Tab(win, socket) { var self = this; - var id = uid++ - , cols = win.cols + var cols = win.cols , rows = win.rows; // TODO: make this an EventEmitter @@ -457,7 +461,7 @@ function Tab(win, socket) { return cancel(ev); }); - this.id = id; + this.id = ''; this.socket = socket; this.window = win; this.button = button; @@ -466,11 +470,12 @@ function Tab(win, socket) { this.open(); win.tabs.push(this); - terms[id] = this; this.socket.emit('create', cols, rows, function(err, data) { if (err) return self._destroy(); self.pty = data.pty; + self.id = data.id; + terms[self.id] = self; self.setProcessName(data.process); }); }; @@ -555,7 +560,7 @@ Tab.prototype._destroy = function() { this.element.parentNode.removeChild(this.element); } - delete terms[this.id]; + if (terms[this.id]) delete terms[this.id]; splice(win.tabs, this); if (win.focused === this) { @@ -776,7 +781,9 @@ function sanitize(text) { */ function load() { - if (socket) return; + if (load.done) return; + load.done = true; + off(document, 'load', load); off(document, 'DOMContentLoaded', load); open(); From 3a62803c7f9474329e8aad790679457dc941552a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 18 Apr 2012 06:35:40 -0500 Subject: [PATCH 040/131] refactor colors 16-255 --- static/term.js | 80 ++++++++++++++++---------------------------------- 1 file changed, 26 insertions(+), 54 deletions(-) diff --git a/static/term.js b/static/term.js index a3f7f92b..4f1da99b 100644 --- a/static/term.js +++ b/static/term.js @@ -96,9 +96,10 @@ function Terminal(cols, rows, handler) { } /** - * Options + * Colors */ +// Colors 0-15 Terminal.colors = [ // dark: '#2e3436', @@ -120,72 +121,39 @@ Terminal.colors = [ '#eeeeec' ]; -// Convert xterm 256 color codes into CSS hex codes. +// Colors 16-255 // Much thanks to TooTallNate for writing this. -Terminal.colors = function() { - var colors - , r - , i - , c; - - // Basic first 16 colors - colors = [ - [0x00, 0x00, 0x00], [0xcd, 0x00, 0x00], - [0x00, 0xcd, 0x00], [0xcd, 0xcd, 0x00], - [0x00, 0x00, 0xee], [0xcd, 0x00, 0xcd], - [0x00, 0xcd, 0xcd], [0xe5, 0xe5, 0xe5], - [0x7f, 0x7f, 0x7f], [0xff, 0x00, 0x00], - [0x00, 0xff, 0x00], [0xff, 0xff, 0x00], - [0x5c, 0x5c, 0xff], [0xff, 0x00, 0xff], - [0x00, 0xff, 0xff], [0xff, 0xff, 0xff] - ]; - - // Numbers used to generate the conversion table - r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff]; - - // Middle 218 colors, 6 sets of 6 tables of 6 +Terminal.colors = (function() { + var colors = Terminal.colors + , r = [0x00, 0x5f, 0x87, 0xaf, 0xd7, 0xff] + , i; + + // 16-231 i = 0; - for (; i < 217; i++) { - colors.push([r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]]); + for (; i < 216; i++) { + out(r[(i / 36) % 6 | 0], r[(i / 6) % 6 | 0], r[i % 6]); } - // Ending with grayscale for the remainder + // 232-255 (grey) i = 0; - for (; i < 23; i++){ + for (; i < 24; i++) { r = 8 + i * 10; - colors.push([r, r, r]); + out(r, r, r); } - // Now convert to CSS hex codes - i = 0; - for (; i < 256; i++) { - c = colors[i]; - - c[0] = c[0].toString(16); - c[1] = c[1].toString(16); - c[2] = c[2].toString(16); - - if (c[0].length < 2) { - c[0] = '0' + c[0]; - } - - if (c[1].length < 2) { - c[1] = '0' + c[1]; - } - - if (c[2].length < 2) { - c[2] = '0' + c[2]; - } - - colors[i] = '#' + c.join(''); + function out(r, g, b) { + colors.push('#' + hex(r) + hex(g) + hex(b)); } - colors = Terminal.colors.concat(colors.slice(16)); + function hex(c) { + c = c.toString(16); + return c.length < 2 ? '0' + c : c; + } return colors; -}(); +})(); -// default bg/fg +// Default BG/FG Terminal.defaultColors = { bg: '#000000', fg: '#f0f0f0' @@ -194,6 +162,10 @@ Terminal.defaultColors = { Terminal.colors[256] = Terminal.defaultColors.bg; Terminal.colors[257] = Terminal.defaultColors.fg; +/** + * Options + */ + Terminal.termName = 'xterm'; Terminal.geometry = [80, 30]; Terminal.cursorBlink = true; From a53ff13e37b10541e54035ef8bd692b2f2ff9e9c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 18 Apr 2012 07:47:39 -0500 Subject: [PATCH 041/131] limit 256 colors sequence at 255. convert 88 to 256. --- static/term.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/static/term.js b/static/term.js index 4f1da99b..5a8c833f 100644 --- a/static/term.js +++ b/static/term.js @@ -2441,13 +2441,17 @@ Terminal.prototype.charAttributes = function(params) { // fg color 256 if (params[i+1] !== 5) continue; i += 2; - p = params[i]; + p = params[i] & 0xff; + // convert 88 colors to 256 + // if (this.is('rxvt-unicode') && p < 88) p = p * 2.9090 | 0; this.curAttr = (this.curAttr & ~(0x1ff << 9)) | (p << 9); } else if (p === 48) { // bg color 256 if (params[i+1] !== 5) continue; i += 2; - p = params[i]; + p = params[i] & 0xff; + // convert 88 colors to 256 + // if (this.is('rxvt-unicode') && p < 88) p = p * 2.9090 | 0; this.curAttr = (this.curAttr & ~0x1ff) | p; } } From 7dfd00b03a86b8b74a6def49beeac15f94c557cd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 18 Apr 2012 07:53:41 -0500 Subject: [PATCH 042/131] add normalMouse property. --- static/term.js | 15 +++++---------- 1 file changed, 5 insertions(+), 10 deletions(-) diff --git a/static/term.js b/static/term.js index 5a8c833f..3355e9c4 100644 --- a/static/term.js +++ b/static/term.js @@ -505,7 +505,7 @@ Terminal.prototype.bindMouse = function() { if (self.vt200Mouse) { // ctrl only mod &= ctrl; - } else if (self.x10Mouse || self.vt300Mouse || self.decLocator) { + } else if (!self.normalMouse) { mod = 0; } @@ -559,8 +559,6 @@ Terminal.prototype.bindMouse = function() { up: ev.type === 'mouseup', wheel: ev.type === wheelEvent, move: ev.type === 'mousemove' - //button: getButton(ev), - //realButton: getButton(ev) & 3 }; } @@ -580,18 +578,13 @@ Terminal.prototype.bindMouse = function() { } // bind events - var motion = !self.x10Mouse - && !self.vt200Mouse - && !self.vt300Mouse - && !self.decLocator; - - if (motion) on(document, 'mousemove', sendMove); + if (self.normalMouse) on(document, 'mousemove', sendMove); // x10 compatibility mode can't send button releases if (!self.x10Mouse) { on(document, 'mouseup', function up(ev) { sendButton(ev); - if (motion) off(document, 'mousemove', sendMove); + if (self.normalMouse) off(document, 'mousemove', sendMove); off(document, 'mouseup', up); return cancel(ev); }); @@ -2923,6 +2916,7 @@ Terminal.prototype.setMode = function(params) { // even if there is no button held down. this.x10Mouse = params === 9; this.vt200Mouse = params === 1000; + this.normalMouse = params > 1000; this.mouseEvents = true; this.element.style.cursor = 'default'; this.log('Binding to mouse events.'); @@ -3106,6 +3100,7 @@ Terminal.prototype.resetMode = function(params) { case 1003: // any event mouse this.x10Mouse = false; this.vt200Mouse = false; + this.normalMouse = false; this.mouseEvents = false; this.element.style.cursor = ''; break; From 65b8e9c3e41f1e019e115f07a29c6658504926a8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 18 Apr 2012 07:58:21 -0500 Subject: [PATCH 043/131] refactor and add properties to constructor. --- static/term.js | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/static/term.js b/static/term.js index 3355e9c4..038ba499 100644 --- a/static/term.js +++ b/static/term.js @@ -70,16 +70,35 @@ function Terminal(cols, rows, handler) { this.scrollTop = 0; this.scrollBottom = this.rows - 1; + // modes this.applicationKeypad = false; this.originMode = false; this.insertMode = false; this.wraparoundMode = false; - this.mouseEvents; - this.sendFocus; - this.setupStops(); this.charset = null; this.normal = null; + // mouse properties + this.decLocator; + this.x10Mouse; + this.vt200Mouse; + this.vt300Mouse; + this.normalMouse; + this.mouseEvents; + this.sendFocus; + this.utfMouse; + this.sgrMouse; + this.urxvtMouse; + + // misc + this.element; + this.children; + this.refreshStart; + this.refreshEnd; + this.savedX; + this.savedY; + this.savedCols; + this.defAttr = (257 << 9) | 256; this.curAttr = this.defAttr; @@ -93,6 +112,9 @@ function Terminal(cols, rows, handler) { while (i--) { this.lines.push(this.blankLine()); } + + this.tabs; + this.setupStops(); } /** From bb58810a78451e9866255c229930f93afe364d73 Mon Sep 17 00:00:00 2001 From: Nicholas Kinsey Date: Tue, 8 May 2012 08:16:06 +1000 Subject: [PATCH 044/131] typos --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index ff00cdd4..14e030de 100644 --- a/README.md +++ b/README.md @@ -70,7 +70,7 @@ JSON file. An example configuration file looks like: "#3465a4", "#75507b", "#06989a", - "#d3d7cf" + "#d3d7cf", "#555753", "#ef2929", "#8ae234", @@ -78,7 +78,7 @@ JSON file. An example configuration file looks like: "#729fcf", "#ad7fa8", "#34e2e2", - "#eeeeec", + "#eeeeec" ] } } From 148500e539c0972673e4a726b03189560e1dc620 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 12 May 2012 11:20:31 -0500 Subject: [PATCH 045/131] fix device status cursor position reporting --- static/term.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/term.js b/static/term.js index 038ba499..179cd775 100644 --- a/static/term.js +++ b/static/term.js @@ -2515,7 +2515,7 @@ Terminal.prototype.deviceStatus = function(params) { switch (params[0]) { case 6: // cursor position - this.send('\x1b[' + this.send('\x1b[?' + (this.y + 1) + ';' + (this.x + 1) From 880d7757bafac245d035dc61931451b5f3d3ad03 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 14 May 2012 12:00:34 -0500 Subject: [PATCH 046/131] add tests --- test/bench.js | 36 ++++++++++++++++++++++++++++++++++++ test/data.diff | 10 ++++++++++ test/index.html | 44 ++++++++++++++++++++++++++++++++++++++++++++ test/index.js | 25 +++++++++++++++++++++++++ 4 files changed, 115 insertions(+) create mode 100644 test/bench.js create mode 100644 test/data.diff create mode 100644 test/index.html create mode 100644 test/index.js diff --git a/test/bench.js b/test/bench.js new file mode 100644 index 00000000..ec6e99f8 --- /dev/null +++ b/test/bench.js @@ -0,0 +1,36 @@ +var element = { + createElement: function() { return element; }, + appendChild: function() {}, + removeChild: function() {}, + addEventListener: function() {}, + removeEventListener: function() {}, + style: {} +}; + +global.window = global; +window.navigator = { userAgent: '' }; +window.document = element; +window.document.body = element; + +var Terminal = require('../static/term'); +Terminal.cursorBlink = false; + +var data = require('./data').data; + +var term = new Terminal(250, 100); +term.open(); + +var time = new Date; +var t = 10; + +while (t--) { + var l = data.length + , i = 0; + + for (; i < l; i++) { + term.write(data[i]); + } +} + +console.log('Completed: %d.', new Date - time); +console.log('Average (?): 13.5k (for ~2.7k writes).'); diff --git a/test/data.diff b/test/data.diff new file mode 100644 index 00000000..fcd8e61a --- /dev/null +++ b/test/data.diff @@ -0,0 +1,10 @@ +167a168,170 +> var stream = fs.createWriteStream(__dirname + '/../test/data.js'); +> stream.write('this.data = [\n'); +> +169a173 +> stream.write(' ' + JSON.stringify(data) + ',\n'); +182a187,189 +> +> stream.write('];\n'); +> stream.end(); diff --git a/test/index.html b/test/index.html new file mode 100644 index 00000000..a7b93dda --- /dev/null +++ b/test/index.html @@ -0,0 +1,44 @@ + +tty.js test + + +

tty.js test

+ + + diff --git a/test/index.js b/test/index.js new file mode 100644 index 00000000..4118cc28 --- /dev/null +++ b/test/index.js @@ -0,0 +1,25 @@ +var path = require('path') + , fs = require('fs'); + +var express = require('express') + , app = express.createServer(); + +app.use(function(req, res, next) { + var setHeader = res.setHeader; + res.setHeader = function(name) { + switch (name) { + case 'Cache-Control': + case 'Last-Modified': + case 'ETag': + return; + } + return setHeader.apply(res, arguments); + }; + next(); +}); + +app.use(express.static(__dirname)); + +app.use(express.static(__dirname + '/../static')); + +app.listen(8080); From c4396aee135768dd184451e7a5738e82eb46c486 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 14 May 2012 15:18:40 -0500 Subject: [PATCH 047/131] add shift modifier support to backspace and tab --- static/term.js | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/static/term.js b/static/term.js index 179cd775..4f147d95 100644 --- a/static/term.js +++ b/static/term.js @@ -1730,11 +1730,18 @@ Terminal.prototype.keyDown = function(ev) { switch (ev.keyCode) { // backspace case 8: + if (ev.shiftKey) { + key = '\x08'; // ^H + break; + } key = '\x7f'; // ^? - //key = '\x08'; // ^H break; // tab case 9: + if (ev.shiftKey) { + key = '\x1b[Z'; + break; + } key = '\t'; break; // return/enter From f3cb68b4062e2c265326f625787fed2c51a2796d Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 May 2012 12:53:03 -0500 Subject: [PATCH 048/131] add EventEmitter and events --- static/term.js | 73 ++++++++++++++++++++++++++++++++++++++++++++++++-- static/tty.js | 71 +++++++++++++++++++++++++++++++++++------------- 2 files changed, 123 insertions(+), 21 deletions(-) diff --git a/static/term.js b/static/term.js index 4f147d95..39752e97 100644 --- a/static/term.js +++ b/static/term.js @@ -37,6 +37,57 @@ var window = this , document = this.document; +/** + * EventEmitter + */ + +function EventEmitter() {} + +EventEmitter.prototype.addListener = function(type, listener) { + this._events = this._events || {}; + this._events[type] = this._events[type] || []; + this._events[type].push(listener); +}; + +EventEmitter.prototype.on = EventEmitter.prototype.addListener; + +EventEmitter.prototype.removeListener = function(type, listener) { + if (!this._events || !this._events[type]) return; + + var obj = this._events[type] + , i = obj.length; + + while (i--) { + if (obj[i] === listener) break; + } + + obj.splice(i, 1); +}; + +EventEmitter.prototype.off = EventEmitter.prototype.removeListener; + +EventEmitter.prototype.once = function(type, listener) { + var self = this; + this.on(type, function on() { + self.removeListener(type, on); + var args = Array.prototype.slice.call(arguments); + return listener.apply(self, args); + }); +}; + +EventEmitter.prototype.emit = function(type) { + if (!this._events || !this._events[type]) return; + + var args = Array.prototype.slice.call(arguments, 1) + , obj = this._events[type] + , l = obj.length + , i = 0; + + for (; i < l; i++) { + obj[i].apply(this, args); + } +}; + /** * States */ @@ -117,6 +168,8 @@ function Terminal(cols, rows, handler) { this.setupStops(); } +inherits(Terminal, EventEmitter); + /** * Colors */ @@ -2155,8 +2208,13 @@ Terminal.prototype.is = function(term) { return (name + '').indexOf(term) === 0; }; -Terminal.prototype.handler = function() {}; -Terminal.prototype.handleTitle = function() {}; +Terminal.prototype.handler = function(data) { + this.emit('data', data); +}; + +Terminal.prototype.handleTitle = function(title) { + this.emit('title', title); +}; /** * ESC @@ -3860,6 +3918,14 @@ function cancel(ev) { return false; } +function inherits(child, parent) { + function f() { + this.constructor = child; + } + f.prototype = parent.prototype; + child.prototype = new f; +} + var isMac = ~navigator.userAgent.indexOf('Mac'); // if bold is broken, we can't @@ -3883,6 +3949,9 @@ var setInterval = this.setInterval; * Expose */ +Terminal.EventEmitter = EventEmitter; +Terminal.inherits = inherits; + if (typeof module !== 'undefined') { module.exports = Terminal; } else { diff --git a/static/tty.js b/static/tty.js index aafa6b16..c812aab5 100644 --- a/static/tty.js +++ b/static/tty.js @@ -11,6 +11,9 @@ var document = this.document , window = this + , inherits = Terminal.inherits + , EventEmitter = Terminal.EventEmitter + , tty = new EventEmitter , root , body , h1; @@ -29,8 +32,7 @@ var windows */ function open() { - var socket = io.connect() - , tty = window.tty; + var socket = io.connect(); root = document.documentElement; body = document.body; @@ -101,6 +103,9 @@ function open() { } } }); + + tty.emit('load'); + tty.emit('open'); } function reset() { @@ -112,8 +117,10 @@ function reset() { windows = []; terms = {}; - window.tty.windows = windows; - window.tty.terms = terms; + tty.windows = windows; + tty.terms = terms; + + tty.emit('reset'); } /** @@ -171,8 +178,15 @@ function Window(socket) { this.createTab(); this.focus(); this.bind(); + + this.tabs[0].once('open', function() { + tty.emit('open window', self); + self.emit('open'); + }); } +inherits(Window, EventEmitter); + Window.prototype.bind = function() { var self = this , el = this.element @@ -221,6 +235,9 @@ Window.prototype.focus = function() { parent.appendChild(this.element); } this.focused.focus(); + + tty.emit('focus window', this); + this.emit('focus'); }; Window.prototype.destroy = function() { @@ -237,10 +254,14 @@ Window.prototype.destroy = function() { this.each(function(term) { term.destroy(); }); + + tty.emit('close window', this); + this.emit('close'); }; Window.prototype.drag = function(ev) { - var el = this.element; + var self = this + , el = this.element; if (this.minimize) return; @@ -269,6 +290,9 @@ Window.prototype.drag = function(ev) { off(document, 'mousemove', move); off(document, 'mouseup', up); + + tty.emit('drag window', self, el.style.left, el.style.top); + self.emit('drag', el.style.left, el.style.top); } on(document, 'mousemove', move); @@ -360,6 +384,9 @@ Window.prototype.maximize = function() { root.className = m.root; self.resize(m.cols, m.rows); + + tty.emit('minimize window', self); + self.emit('minimize'); }; window.scrollTo(0, 0); @@ -380,6 +407,9 @@ Window.prototype.maximize = function() { root.className = 'maximized'; this.resize(x, y); + + tty.emit('maximize window', this); + this.emit('maximize'); }; Window.prototype.resize = function(cols, rows) { @@ -388,6 +418,8 @@ Window.prototype.resize = function(cols, rows) { this.each(function(term) { term.resize(cols, rows); }); + tty.emit('resize window', this, cols, rows); + this.emit('resize', cols, rows); }; Window.prototype.each = function(func) { @@ -398,7 +430,7 @@ Window.prototype.each = function(func) { }; Window.prototype.createTab = function() { - new Tab(this, this.socket); + return new Tab(this, this.socket); }; Window.prototype.highlight = function() { @@ -477,6 +509,8 @@ function Tab(win, socket) { self.id = data.id; terms[self.id] = self; self.setProcessName(data.process); + tty.emit('open tab', self); + self.emit('open'); }); }; @@ -540,6 +574,9 @@ Tab.prototype.focus = function() { this._focus(); win.focus(); + + tty.emit('focus tab', this); + this.emit('focus'); }; Tab.prototype._resize = Tab.prototype.resize; @@ -547,6 +584,8 @@ Tab.prototype._resize = Tab.prototype.resize; Tab.prototype.resize = function(cols, rows) { this.socket.emit('resize', this.id, cols, rows); this._resize(cols, rows); + tty.emit('resize tab', this, cols, rows); + this.emit('resize', cols, rows); }; Tab.prototype._destroy = function() { @@ -581,6 +620,8 @@ Tab.prototype.destroy = function() { if (this.destroyed) return; this.socket.emit('kill', this.id); this._destroy(); + tty.emit('close tab', this); + this.emit('close'); }; Tab.prototype._keyDown = Tab.prototype.keyDown; @@ -732,14 +773,6 @@ Tab.prototype.setProcessName = function(name) { * Helpers */ -function inherits(child, parent) { - function f() { - this.constructor = child; - } - f.prototype = parent.prototype; - child.prototype = new f; -} - function indexOf(obj, el) { var i = obj.length; while (i--) { @@ -797,11 +830,11 @@ setTimeout(load, 200); * Expose */ -this.tty = { - Window: Window, - Tab: Tab, - Terminal: Terminal -}; +tty.Window = Window; +tty.Tab = Tab; +tty.Terminal = Terminal; + +this.tty = tty; }).call(function() { return this || (typeof window !== 'undefined' ? window : global); From 3ebfc7bfb982814ad1328f1e7685a1f3225c1e95 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 29 May 2012 13:10:42 -0500 Subject: [PATCH 049/131] prevent code duplication --- static/term.js | 4 ++++ static/tty.js | 47 ++++++++++++++++++++++++++--------------------- 2 files changed, 30 insertions(+), 21 deletions(-) diff --git a/static/term.js b/static/term.js index 39752e97..2c9e6b86 100644 --- a/static/term.js +++ b/static/term.js @@ -3950,7 +3950,11 @@ var setInterval = this.setInterval; */ Terminal.EventEmitter = EventEmitter; +Terminal.isMac = isMac; Terminal.inherits = inherits; +Terminal.on = on; +Terminal.off = off; +Terminal.cancel = cancel; if (typeof module !== 'undefined') { module.exports = Terminal; diff --git a/static/tty.js b/static/tty.js index c812aab5..e63ddba7 100644 --- a/static/tty.js +++ b/static/tty.js @@ -11,15 +11,33 @@ var document = this.document , window = this - , inherits = Terminal.inherits - , EventEmitter = Terminal.EventEmitter - , tty = new EventEmitter , root , body , h1; +/** + * Initial Document Title + */ + var initialTitle = document.title; +/** + * Helpers + */ + +var EventEmitter = Terminal.EventEmitter + , isMac = Terminal.isMac + , inherits = Terminal.inherits + , on = Terminal.on + , off = Terminal.off + , cancel = Terminal.cancel; + +/** + * tty + */ + +var tty = new EventEmitter; + /** * Shared */ @@ -65,6 +83,7 @@ function open() { socket.on('connect', function() { reset(); new Window(socket); + tty.emit('connect'); }); socket.on('data', function(id, data) { @@ -516,10 +535,14 @@ function Tab(win, socket) { inherits(Tab, Terminal); +// We could just hook in `tab.on('data', ...)` +// in the constructor, but this is faster. Tab.prototype.handler = function(data) { this.socket.emit('data', this.id, data); }; +// We could just hook in `tab.on('title', ...)` +// in the constructor, but this is faster. Tab.prototype.handleTitle = function(title) { if (!title) return; @@ -786,24 +809,6 @@ function splice(obj, el) { if (~i) obj.splice(i, 1); } -function on(el, type, handler, capture) { - el.addEventListener(type, handler, capture || false); -} - -function off(el, type, handler, capture) { - el.removeEventListener(type, handler, capture || false); -} - -function cancel(ev) { - if (ev.preventDefault) ev.preventDefault(); - ev.returnValue = false; - if (ev.stopPropagation) ev.stopPropagation(); - ev.cancelBubble = true; - return false; -} - -var isMac = ~navigator.userAgent.indexOf('Mac'); - function sanitize(text) { if (!text) return ''; return (text + '').replace(/[&<>]/g, '') From cea7552f02b2503f4dfa8f9b411fa4289b0e3c05 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 11:42:48 -0500 Subject: [PATCH 050/131] add experimental key hooks. fix misc event related things. --- static/term.js | 28 ++++++++++----- static/tty.js | 95 +++++++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 113 insertions(+), 10 deletions(-) diff --git a/static/term.js b/static/term.js index 2c9e6b86..9bac59b5 100644 --- a/static/term.js +++ b/static/term.js @@ -58,20 +58,20 @@ EventEmitter.prototype.removeListener = function(type, listener) { , i = obj.length; while (i--) { - if (obj[i] === listener) break; + if (obj[i] === listener) { + obj.splice(i, 1); + return; + } } - - obj.splice(i, 1); }; EventEmitter.prototype.off = EventEmitter.prototype.removeListener; EventEmitter.prototype.once = function(type, listener) { - var self = this; this.on(type, function on() { - self.removeListener(type, on); var args = Array.prototype.slice.call(arguments); - return listener.apply(self, args); + this.removeListener(type, on); + return listener.apply(this, args); }); }; @@ -105,9 +105,11 @@ var normal = 0 */ function Terminal(cols, rows, handler) { - this.cols = cols; - this.rows = rows; - if (handler) this.handler = handler; + this.cols = cols || Terminal.geometry[0]; + this.rows = rows || Terminal.geometry[1]; + + //if (handler) this.handler = handler; + if (handler) this.on('data', handler); this.ybase = 0; this.ydisp = 0; @@ -1969,9 +1971,14 @@ Terminal.prototype.keyDown = function(ev) { break; } + this.emit('keydown', ev); + if (key) { + this.emit('key', key, ev); + this.showCursor(); this.handler(key); + return cancel(ev); } @@ -1997,6 +2004,9 @@ Terminal.prototype.keyPress = function(ev) { key = String.fromCharCode(key); + this.emit('keypress', key, ev); + this.emit('key', key, ev); + this.showCursor(); this.handler(key); diff --git a/static/tty.js b/static/tty.js index e63ddba7..6542f363 100644 --- a/static/tty.js +++ b/static/tty.js @@ -173,7 +173,7 @@ function Window(socket) { title.className = 'title'; title.innerHTML = ''; - this.socket = socket; + this.socket = socket || tty.socket; this.element = el; this.grip = grip; this.bar = bar; @@ -647,6 +647,89 @@ Tab.prototype.destroy = function() { this.emit('close'); }; +Tab.prototype.__bindKeys = function() { + this.on('key', function(key, ev) { + // ^A for screen-key-like prefix. + if (Terminal.screenKeys) { + if (this.pendingKey) { + this.__ignoreNext(); + this.pendingKey = false; + this.__specialKeyHandler(key); + return; + } + + // ^A + if (key === '\x01') { + this.__ignoreNext(); + this.pendingKey = true; + return; + } + } + + // Alt-` to quickly swap between windows. + if (key === '\x1b`') { + var i = indexOf(windows, this.window) + 1; + this.__ignoreNext(); + if (windows[i]) return windows[i].highlight(); + if (windows[0]) return windows[0].highlight(); + + return this.window.highlight(); + } + + // URXVT Keys for tab navigation and creation. + // Shift-Left, Shift-Right, Shift-Down + if (key === '\x1b[1;2D') { + this.__ignoreNext(); + return this.window.previousTab(); + } else if (key === '\x1b[1;2B') { + this.__ignoreNext(); + return this.window.nextTab(); + } else if (key === '\x1b[1;2C') { + this.__ignoreNext(); + return this.window.createTab(); + } + }); +}; + +// tmux/screen-like keys +Tab.prototype.__specialKeyHandler = function(key) { + var win = this.window; + + switch (key) { + case '\x01': // ^A + this.send(key); + break; + case 'c': + win.createTab(); + break; + case 'k': + win.focused.destroy(); + break; + case 'w': // tmux + case '"': // screen + break; + default: + if (key >= '0' && key <= '9') { + key = +key; + // 1-indexed + key--; + if (!~key) key = 9; + if (win.tabs[key]) { + win.tabs[key].focus(); + } + } + break; + } +}; + +Tab.prototype.__ignoreNext = function() { + // Don't send the next key. + var handler = this.handler; + this.handler = function() { + this.handler = handler; + }; +}; + Tab.prototype._keyDown = Tab.prototype.keyDown; Tab.prototype.keyDown = function(ev) { @@ -839,6 +922,16 @@ tty.Window = Window; tty.Tab = Tab; tty.Terminal = Terminal; +tty.createWindow = function() { + if (!tty.socket) { + tty.once('load', function() { + new Window; + }); + return; + } + return new Window; +}; + this.tty = tty; }).call(function() { From 5f5be6602aac81bd87a014c22d5f8181508ecd63 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 14:33:17 -0500 Subject: [PATCH 051/131] require pty.js v0.1.2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index d0240478..51659234 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,6 @@ "dependencies": { "express": ">= 2.5.8", "socket.io": ">= 0.8.7", - "pty.js": ">= 0.0.8" + "pty.js": ">= 0.1.2" } } From ad15a3eecc11b9e3f7ab29f5331f039b446d848f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 21:06:14 -0500 Subject: [PATCH 052/131] make Terminal more streamlike --- static/term.js | 25 +++++++++++++++++++++++++ static/tty.js | 4 ++++ 2 files changed, 29 insertions(+) diff --git a/static/term.js b/static/term.js index 9bac59b5..e8f69307 100644 --- a/static/term.js +++ b/static/term.js @@ -152,6 +152,10 @@ function Terminal(cols, rows, handler) { this.savedY; this.savedCols; + // stream + this.readable = true; + this.writable = true; + this.defAttr = (257 << 9) | 256; this.curAttr = this.defAttr; @@ -365,6 +369,8 @@ Terminal.prototype.open = function() { // sync default bg/fg colors this.element.style.backgroundColor = Terminal.defaultColors.bg; this.element.style.color = Terminal.defaultColors.fg; + + //this.emit('open'); }; // XTerm mouse events @@ -459,6 +465,12 @@ Terminal.prototype.bindMouse = function() { // vt300: ^[[ 24(1/3/5)~ [ Cx , Cy ] \r // locator: CSI P e ; P b ; P r ; P c ; P p & w function sendEvent(button, pos) { + // self.emit('mouse', { + // x: pos.x - 32, + // y: pos.x - 32, + // button: button + // }); + if (self.vt300Mouse) { // NOTE: Unstable. // http://www.vt100.net/docs/vt3xx-gp/chapter15.html @@ -693,6 +705,19 @@ Terminal.prototype.bindMouse = function() { }); }; +/** + * Destroy Terminal + */ + +Terminal.prototype.destroy = function() { + this.readable = false; + this.writable = false; + this._events = {}; + this.handler = function() {}; + this.write = function() {}; + //this.emit('close'); +}; + /** * Rendering Engine */ diff --git a/static/tty.js b/static/tty.js index 6542f363..eab92d06 100644 --- a/static/tty.js +++ b/static/tty.js @@ -611,6 +611,8 @@ Tab.prototype.resize = function(cols, rows) { this.emit('resize', cols, rows); }; +Tab.prototype.__destroy = Tab.prototype.destroy; + Tab.prototype._destroy = function() { if (this.destroyed) return; this.destroyed = true; @@ -637,6 +639,8 @@ Tab.prototype._destroy = function() { // document.title = initialTitle; // if (h1) h1.innerHTML = initialTitle; // } + + this.__destroy(); }; Tab.prototype.destroy = function() { From 0d791ee10122ee87fb414328d39ffd21026b30ba Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 21:17:24 -0500 Subject: [PATCH 053/131] handle alt-` --- static/term.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/static/term.js b/static/term.js index e8f69307..82937789 100644 --- a/static/term.js +++ b/static/term.js @@ -1989,6 +1989,8 @@ Terminal.prototype.keyDown = function(ev) { } else if ((!isMac && ev.altKey) || (isMac && ev.metaKey)) { if (ev.keyCode >= 65 && ev.keyCode <= 90) { key = '\x1b' + String.fromCharCode(ev.keyCode + 32); + } else if (ev.keyCode === 192) { + key = '\x1b`'; } else if (ev.keyCode >= 48 && ev.keyCode <= 57) { key = '\x1b' + (ev.keyCode - 48); } From 70c9fbf194d0ee9ee7ce0f56348fbd5bc749abe4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 21:27:31 -0500 Subject: [PATCH 054/131] hook into key events instead of hooking key handler. --- static/tty.js | 103 ++++++-------------------------------------------- 1 file changed, 11 insertions(+), 92 deletions(-) diff --git a/static/tty.js b/static/tty.js index eab92d06..c2d55e76 100644 --- a/static/tty.js +++ b/static/tty.js @@ -519,6 +519,7 @@ function Tab(win, socket) { this.element = null; this.process = ''; this.open(); + this.hookKeys(); win.tabs.push(this); @@ -651,20 +652,20 @@ Tab.prototype.destroy = function() { this.emit('close'); }; -Tab.prototype.__bindKeys = function() { +Tab.prototype.hookKeys = function() { this.on('key', function(key, ev) { // ^A for screen-key-like prefix. if (Terminal.screenKeys) { if (this.pendingKey) { - this.__ignoreNext(); + this._ignoreNext(); this.pendingKey = false; - this.__specialKeyHandler(key); + this.specialKeyHandler(key); return; } // ^A if (key === '\x01') { - this.__ignoreNext(); + this._ignoreNext(); this.pendingKey = true; return; } @@ -673,7 +674,7 @@ Tab.prototype.__bindKeys = function() { // Alt-` to quickly swap between windows. if (key === '\x1b`') { var i = indexOf(windows, this.window) + 1; - this.__ignoreNext(); + this._ignoreNext(); if (windows[i]) return windows[i].highlight(); if (windows[0]) return windows[0].highlight(); @@ -683,20 +684,20 @@ Tab.prototype.__bindKeys = function() { // URXVT Keys for tab navigation and creation. // Shift-Left, Shift-Right, Shift-Down if (key === '\x1b[1;2D') { - this.__ignoreNext(); + this._ignoreNext(); return this.window.previousTab(); } else if (key === '\x1b[1;2B') { - this.__ignoreNext(); + this._ignoreNext(); return this.window.nextTab(); } else if (key === '\x1b[1;2C') { - this.__ignoreNext(); + this._ignoreNext(); return this.window.createTab(); } }); }; // tmux/screen-like keys -Tab.prototype.__specialKeyHandler = function(key) { +Tab.prototype.specialKeyHandler = function(key) { var win = this.window; switch (key) { @@ -726,7 +727,7 @@ Tab.prototype.__specialKeyHandler = function(key) { } }; -Tab.prototype.__ignoreNext = function() { +Tab.prototype._ignoreNext = function() { // Don't send the next key. var handler = this.handler; this.handler = function() { @@ -734,88 +735,6 @@ Tab.prototype.__ignoreNext = function() { }; }; -Tab.prototype._keyDown = Tab.prototype.keyDown; - -Tab.prototype.keyDown = function(ev) { - if (this.pendingKey) { - this.pendingKey = false; - return this.specialKeyHandler(ev); - } - - // ^A for screen-key-like prefix. - if (Terminal.screenKeys && ev.ctrlKey && ev.keyCode === 65) { - this.pendingKey = true; - return cancel(ev); - } - - // Alt-` to quickly swap between windows. - if (ev.keyCode === 192 - && ((!isMac && ev.altKey) - || (isMac && ev.metaKey))) { - cancel(ev); - - var i = indexOf(windows, this.window) + 1; - if (windows[i]) return windows[i].highlight(); - if (windows[0]) return windows[0].highlight(); - - return this.window.highlight(); - } - - // URXVT Keys for tab navigation and creation. - // Shift-Left, Shift-Right, Shift-Down - if (ev.shiftKey && (ev.keyCode >= 37 && ev.keyCode <= 40)) { - cancel(ev); - - if (ev.keyCode === 37) { - return this.window.previousTab(); - } else if (ev.keyCode === 39) { - return this.window.nextTab(); - } - - return this.window.createTab(); - } - - // Pass to terminal key handler. - return this._keyDown(ev); -}; - -// tmux/screen-like keys -Tab.prototype.specialKeyHandler = function(ev) { - var win = this.window - , key = ev.keyCode; - - switch (key) { - case 65: // a - if (ev.ctrlKey) { - return this._keyDown(ev); - } - break; - case 67: // c - win.createTab(); - break; - case 75: // k - win.focused.destroy(); - break; - case 87: // w (tmux key) - case 222: // " - mac (screen key) - case 192: // " - windows (screen key) - break; - default: // 0 - 9 - if (key >= 48 && key <= 57) { - key -= 48; - // 1-indexed - key--; - if (!~key) key = 9; - if (win.tabs[key]) { - win.tabs[key].focus(); - } - } - break; - } - - return cancel(ev); -}; - /** * Program-specific Features */ From e42db4c28a9c702712bbff70a97563e99c2d58a3 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 21:48:33 -0500 Subject: [PATCH 055/131] expose everything. better namespacing. --- static/tty.js | 125 ++++++++++++++++++++++++-------------------------- 1 file changed, 61 insertions(+), 64 deletions(-) diff --git a/static/tty.js b/static/tty.js index c2d55e76..41508ad0 100644 --- a/static/tty.js +++ b/static/tty.js @@ -13,7 +13,9 @@ var document = this.document , window = this , root , body - , h1; + , h1 + , open + , lights; /** * Initial Document Title @@ -42,33 +44,37 @@ var tty = new EventEmitter; * Shared */ -var windows - , terms; +tty.socket; +tty.windows; +tty.terms; +tty.elements; /** * Open */ -function open() { - var socket = io.connect(); - - root = document.documentElement; - body = document.body; - h1 = document.getElementsByTagName('h1')[0]; - - windows = []; - terms = {}; - - tty.socket = socket; - tty.windows = windows; - tty.terms = terms; +tty.open = function() { + tty.socket = io.connect(); + tty.windows = []; + tty.terms = {}; + + tty.elements = { + root: document.documentElement, + body: document.body, + h1: document.getElementsByTagName('h1')[0], + open: document.getElementById('open'), + lights: document.getElementById('lights') + }; - var open = document.getElementById('open') - , lights = document.getElementById('lights'); + root = tty.elements.root; + body = tty.elements.body; + h1 = tty.elements.h1; + open = tty.elements.open; + lights = tty.elements.lights; if (open) { on(open, 'click', function() { - new Window(socket); + new Window; }); } @@ -80,20 +86,20 @@ function open() { }); } - socket.on('connect', function() { - reset(); - new Window(socket); + tty.socket.on('connect', function() { + tty.reset(); + new Window; tty.emit('connect'); }); - socket.on('data', function(id, data) { - if (!terms[id]) return; - terms[id].write(data); + tty.socket.on('data', function(id, data) { + if (!tty.terms[id]) return; + tty.terms[id].write(data); }); - socket.on('kill', function(id) { - if (!terms[id]) return; - terms[id]._destroy(); + tty.socket.on('kill', function(id) { + if (!tty.terms[id]) return; + tty.terms[id]._destroy(); }); // We would need to poll the os on the serverside @@ -102,20 +108,20 @@ function open() { // clientside, rather than poll on the // server, and *then* send it to the client. setInterval(function() { - var i = windows.length; + var i = tty.windows.length; while (i--) { - if (!windows[i].focused) continue; - windows[i].focused.pollProcessName(); + if (!tty.windows[i].focused) continue; + tty.windows[i].focused.pollProcessName(); } }, 2 * 1000); // Keep windows maximized. on(window, 'resize', function(ev) { - var i = windows.length + var i = tty.windows.length , win; while (i--) { - win = windows[i]; + win = tty.windows[i]; if (win.minimize) { win.minimize(); win.maximize(); @@ -125,22 +131,23 @@ function open() { tty.emit('load'); tty.emit('open'); -} +}; + +/** + * Reset + */ -function reset() { - var i = windows.length; +tty.reset = function() { + var i = tty.windows.length; while (i--) { - windows[i].destroy(); + tty.windows[i].destroy(); } - windows = []; - terms = {}; - - tty.windows = windows; - tty.terms = terms; + tty.windows = []; + tty.terms = {}; tty.emit('reset'); -} +}; /** * Window @@ -192,7 +199,7 @@ function Window(socket) { bar.appendChild(title); body.appendChild(el); - windows.push(this); + tty.windows.push(this); this.createTab(); this.focus(); @@ -265,8 +272,8 @@ Window.prototype.destroy = function() { if (this.minimize) this.minimize(); - splice(windows, this); - if (windows.length) windows[0].focus(); + splice(tty.windows, this); + if (tty.windows.length) tty.windows[0].focus(); this.element.parentNode.removeChild(this.element); @@ -513,7 +520,7 @@ function Tab(win, socket) { }); this.id = ''; - this.socket = socket; + this.socket = socket || tty.socket; this.window = win; this.button = button; this.element = null; @@ -527,7 +534,7 @@ function Tab(win, socket) { if (err) return self._destroy(); self.pty = data.pty; self.id = data.id; - terms[self.id] = self; + tty.terms[self.id] = self; self.setProcessName(data.process); tty.emit('open tab', self); self.emit('open'); @@ -625,7 +632,7 @@ Tab.prototype._destroy = function() { this.element.parentNode.removeChild(this.element); } - if (terms[this.id]) delete terms[this.id]; + if (tty.terms[this.id]) delete tty.terms[this.id]; splice(win.tabs, this); if (win.focused === this) { @@ -636,7 +643,7 @@ Tab.prototype._destroy = function() { win.destroy(); } - // if (!windows.length) { + // if (!tty.windows.length) { // document.title = initialTitle; // if (h1) h1.innerHTML = initialTitle; // } @@ -673,10 +680,10 @@ Tab.prototype.hookKeys = function() { // Alt-` to quickly swap between windows. if (key === '\x1b`') { - var i = indexOf(windows, this.window) + 1; + var i = indexOf(tty.windows, this.window) + 1; this._ignoreNext(); - if (windows[i]) return windows[i].highlight(); - if (windows[0]) return windows[0].highlight(); + if (tty.windows[i]) return tty.windows[i].highlight(); + if (tty.windows[0]) return tty.windows[0].highlight(); return this.window.highlight(); } @@ -830,7 +837,7 @@ function load() { off(document, 'load', load); off(document, 'DOMContentLoaded', load); - open(); + tty.open(); } on(document, 'load', load); @@ -845,16 +852,6 @@ tty.Window = Window; tty.Tab = Tab; tty.Terminal = Terminal; -tty.createWindow = function() { - if (!tty.socket) { - tty.once('load', function() { - new Window; - }); - return; - } - return new Window; -}; - this.tty = tty; }).call(function() { From 4155618bb89684ddab1816aba07f5f3fba56b81a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 30 May 2012 21:59:10 -0500 Subject: [PATCH 056/131] have term.js set title. --- static/term.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/static/term.js b/static/term.js index 82937789..202eac55 100644 --- a/static/term.js +++ b/static/term.js @@ -1184,7 +1184,8 @@ Terminal.prototype.write = function(data) { case 1: case 2: if (this.params[1]) { - this.handleTitle(this.params[1]); + this.title = this.params[1]; + this.handleTitle(this.title); } break; case 3: From 25cb2cca0d537d98f2d8634fd64eaab9f6f1ac9c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Jun 2012 11:42:18 -0500 Subject: [PATCH 057/131] major refactor of lib/tty.js. --- bin/tty.js | 10 +- lib/config.js | 111 ++++++++---- lib/tty.js | 488 ++++++++++++++++++++++++++++++++------------------ static/tty.js | 50 ++++-- 4 files changed, 441 insertions(+), 218 deletions(-) diff --git a/bin/tty.js b/bin/tty.js index ade33508..ba11552e 100755 --- a/bin/tty.js +++ b/bin/tty.js @@ -1,3 +1,11 @@ #!/usr/bin/env node -require('../'); +process.title = 'tty.js'; + +var tty = require('../'); +var conf = tty.config.readConfig(); +var app = tty.createServer(conf); + +app.listen(); + +module.exports = app; diff --git a/lib/config.js b/lib/config.js index ecc7ed38..57bbc248 100644 --- a/lib/config.js +++ b/lib/config.js @@ -39,23 +39,22 @@ var schema = { } }; +var options; + /** * Read Config */ -function readConfig(name) { +function readConfig(file) { var home = process.env.HOME , conf = {} - , opt , dir , json; - opt = parseArg(); - - if (opt.config) { - opt.config = path.resolve(process.cwd(), opt.config); - dir = path.dirname(opt.config); - json = opt.config; + if (file || options.config) { + file = path.resolve(process.cwd(), file || options.config); + dir = path.dirname(file); + json = options.config; } else { dir = path.join(home, '.tty.js'); json = path.join(dir, 'config.json'); @@ -65,21 +64,20 @@ function readConfig(name) { if (!fs.statSync(dir).isDirectory()) { json = dir; dir = home; - tryRead = function() {}; } // read conf conf = JSON.parse(fs.readFileSync(json, 'utf8')); // ensure schema - ensure(schema, conf); + ensure(conf, schema); } else { if (!exists(dir)) { fs.mkdirSync(dir, 0700); } // ensure schema - ensure(schema, conf); + ensure(conf, schema); fs.writeFileSync(json, JSON.stringify(conf, null, 2)); fs.chmodSync(json, 0600); @@ -89,14 +87,48 @@ function readConfig(name) { conf.dir = dir; conf.json = json; + // flag + conf.__read = true; + + return checkConfig(conf); +} + +function checkConfig(conf) { + conf = conf || {}; + + if (typeof conf === 'string') { + return readConfig(conf); + } + + if (conf.__check) return conf; + + // flag + conf.__check = true; + + // maybe remove? + ensure(conf, schema); + + if (!conf.dir) { + conf.dir = process.env.HOME + '/.tty.js'; + //conf.dir = process.cwd(); + } + + if (!conf.json) { + conf.json = process.env.HOME + '/.tty.js/config.json'; + //conf.json = process.cwd() + '/config.json'; + } + + var dir = conf.dir + , json = conf.json; + // merge options - merge(opt, conf); + merge(conf, options); // check legacy features checkLegacy(conf); // key and cert - conf.https = { + conf.https = conf.https || { key: tryRead(dir, 'server.key'), cert: tryRead(dir, 'server.crt') }; @@ -107,7 +139,7 @@ function readConfig(name) { } // static directory - conf.static = tryResolve(dir, 'static'); + conf.static = conf.static || tryResolve(dir, 'static'); // Path to shell, or the process to execute in the terminal. conf.shell = conf.shell || process.env.SHELL || 'sh'; @@ -130,10 +162,12 @@ function readConfig(name) { conf.limitGlobal = conf.limitGlobal || Infinity; // users - if (conf.users && !Object.keys(conf.users).length) delete conf.users; + if (conf.users && !Object.keys(conf.users).length) { + delete conf.users; + } // hooks - conf.hooks = tryRequire(dir, 'hooks.js'); + conf.hooks = conf.hooks || tryRequire(dir, 'hooks.js'); // cwd if (conf.cwd) { @@ -154,7 +188,6 @@ function checkLegacy(conf) { if (conf.auth && conf.auth.username && !conf.auth.disabled) { conf.users[conf.auth.username] = conf.auth.password; } - // out.push('`auth` is deprecated, please use `users` instead.'); console.error('`auth` is deprecated, please use `users` instead.'); } @@ -163,9 +196,6 @@ function checkLegacy(conf) { key: tryRead(conf.dir, conf.https.key), cert: tryRead(conf.dir, conf.https.cert) }; - // out.push('' - // + '`https` is deprecated, pleased include ' - // + '`~/.tty.js/server.crt`, and `~/.tty.js/server.key` instead.'); } if (conf.userScript) { @@ -188,16 +218,10 @@ function checkLegacy(conf) { if (conf.static) { conf.static = tryResolve(conf.dir, conf.static); - // out.push('' - // + '`static` is deprecated, please place a ' - // + 'directory called `static` in `~/.tty.js` instead.'); } if (conf.hooks) { conf.hooks = tryRequire(conf.dir, conf.hooks); - // out.push('' - // + '`hooks` is deprecated, please place ' - // + '`hooks.js` in `~/.tty.js/hooks.js` instead.'); } if (out.length) { @@ -225,7 +249,7 @@ function daemonize() { }).join(' '); code = '(IS_DAEMONIC=1 setsid ' + argv + ' > /dev/null 2>& 1 &)'; - spawn('/bin/sh', [ '-c', code ]).on('exit', function(code) { + spawn('/bin/sh', ['-c', code]).on('exit', function(code) { process.exit(code || 0); }); @@ -308,6 +332,8 @@ function parseArg() { return opt; } +options = exports.options = parseArg(); + /** * Xresources */ @@ -379,19 +405,40 @@ function exists(file) { } function merge(i, o) { - Object.keys(i).forEach(function(key) { - o[key] = i[key]; + Object.keys(o).forEach(function(key) { + i[key] = o[key]; }); + return i; } function ensure(i, o) { - Object.keys(i).forEach(function(key) { - if (!o[key]) o[key] = i[key]; + Object.keys(o).forEach(function(key) { + if (!i[key]) i[key] = o[key]; }); + return i; +} + +function clone(obj) { + return merge({}, obj); } /** * Expose */ -module.exports = readConfig(); +exports.readConfig = readConfig; +exports.checkConfig = checkConfig; +exports.schema = clone(schema); +exports.xresources = readResources(); + +exports.helpers = { + tryRequire: tryRequire, + tryResolve: tryResolve, + tryRead: tryRead, + exists: exists, + merge: merge, + ensure: ensure, + clone: clone +}; + +merge(exports, exports.helpers); diff --git a/lib/tty.js b/lib/tty.js index 55906713..61711353 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -3,8 +3,6 @@ * Copyright (c) 2012, Christopher Jeffrey (MIT License) */ -process.title = 'tty.js'; - /** * Modules */ @@ -17,57 +15,96 @@ var express = require('express') , io = require('socket.io') , pty = require('pty.js'); -/** - * Config - */ - -var conf = require('./config'); +var config = require('./config'); /** - * Auth + * Server */ -var auth = basicAuth(); +function Server(conf) { + conf = config.checkConfig(conf); -/** - * App & Middleware - */ + var self = conf.https && conf.https.key + ? express.createServer(conf.https) + : express.createServer(); -var app = conf.https && conf.https.key - ? express.createServer(conf.https) - : express.createServer(); - -app.use(function(req, res, next) { - var setHeader = res.setHeader; - res.setHeader = function(name) { - switch (name) { - case 'Cache-Control': - case 'Last-Modified': - case 'ETag': - return; - } - return setHeader.apply(res, arguments); - }; - next(); -}); + // We can't inherit from express + // javascript-style, so we need to + // do it this way... + Object.keys(Server.prototype).forEach(function(key) { + self[key] = Server.prototype[key]; + }); + + self.sessions = {}; + self.conf = conf; + self._auth = self._basicAuth(); + self.io = io.listen(self, { + log: false + }); -app.use(auth); + self.init(); -if (conf.static) { - app.use(express.static(conf.static)); + return self; } -app.use(express.favicon(__dirname + '/../static/favicon.ico')); +Server.createServer = Server; + +Server.prototype.init = function() { + this.init = function() {}; + this.initMiddleware(); + this.initRoutes(); + this.initIO(); +}; + +Server.prototype.initMiddleware = function() { + var conf = this.conf; + + this.use(function(req, res, next) { + var setHeader = res.setHeader; + res.setHeader = function(name) { + switch (name) { + case 'Cache-Control': + case 'Last-Modified': + case 'ETag': + return; + } + return setHeader.apply(res, arguments); + }; + next(); + }); -app.use(app.router); + this.use(this._auth); -app.use(express.static(__dirname + '/../static')); + if (conf.static) { + this.use(express.static(conf.static)); + } -/** - * Expose Terminal Options - */ + // var icon = conf.static + '/favicon.ico'; + // if (!conf.static || !path.existsSync(icon)) { + // icon = __dirname + '/../static/favicon.ico'; + // } + // this.use(express.favicon(icon)); + + // If there is a custom favicon in the custom + // static directory, this will be ignored. + this.use(express.favicon(__dirname + '/../static/favicon.ico')); + + this.use(this.router); + + this.use(express.static(__dirname + '/../static')); +}; + +Server.prototype.initRoutes = function() { + var self = this; + this.get('/options.js', function(req, res, next) { + return self.handleOptions(req, res, next); + }); +}; + +Server.prototype.handleOptions = function(req, res, next) { + var self = this; + var conf = this.conf; -app.get('/options.js', function(req, res, next) { res.contentType('.js'); fs.readFile(conf.json, 'utf8', function(err, data) { try { @@ -77,7 +114,7 @@ app.get('/options.js', function(req, res, next) { } if (data.term) { - Object.keys(data).forEach(function(key) { + Object.keys(data.term).forEach(function(key) { conf.term[key] = data.term[key]; }); } @@ -89,166 +126,72 @@ app.get('/options.js', function(req, res, next) { + applyConfig + ')();'); }); -}); +}; -function applyConfig() { - for (var key in Terminal.options) { - if (Object.prototype.hasOwnProperty.call(Terminal.options, key)) { - if (key === 'colors') { - var l = Terminal.options.colors.length - , i = 0; - - for (; i < l; i++) { - Terminal.colors[i] = Terminal.options.colors[i]; - } - } else { - Terminal[key] = Terminal.options[key]; - } - } - } - delete Terminal.options; -} +Server.prototype.initIO = function() { + var self = this; + var io = this.io; -/** - * Sockets - */ + io.configure(function() { + io.disable('log'); + }); -var io = io.listen(app) - , state = {}; + io.set('authorization', function(data, next) { + return self.handleAuth(data, next); + }); -io.configure(function() { - io.disable('log'); -}); + io.sockets.on('connection', function(socket) { + return self.handleConnection(socket); + }); +}; -io.set('authorization', function(data, next) { +Server.prototype.handleAuth = function(data, next) { + var io = this.io; data.__proto__ = EventEmitter.prototype; - auth(data, null, function(err) { + this._auth(data, null, function(err) { data.user = data.remoteUser || data.user; return !err ? next(null, true) : next(err); }); -}); +}; -io.sockets.on('connection', function(socket) { - var req = socket.handshake - , terms = {}; +Server.prototype.handleConnection = function(socket) { + var session = new Session(this, socket); - // Kill older session. - if (conf.sessions && conf.users) { - if (state[req.user]) { - try { - state[req.user].disconnect(); - } catch (e) { - ; - } - } - state[req.user] = socket; - } + //this.sessions[session.id] = session; + //this.sessions.push(session); socket.on('create', function(cols, rows, func) { - var len = Object.keys(terms).length - , term - , id; - - if (len >= conf.limitPerUser || pty.total >= conf.limitGlobal) { - return func('Terminal limit.'); - } - - term = pty.fork(conf.shell, conf.shellArgs, { - name: conf.termName, - cols: cols, - rows: rows, - cwd: conf.cwd || process.env.HOME - }); - - id = term.pty; - terms[id] = term; - - term.on('data', function(data) { - socket.emit('data', id, data); - }); - - term.on('close', function() { - // make sure it closes - // on the clientside - socket.emit('kill', id); - - // ensure removal - if (terms[id]) delete terms[id]; - - console.log( - 'Closed pty (%s): %d.', - term.pty, term.fd); - }); - - console.log('' - + 'Created shell with pty (%s) master/slave' - + ' pair (master: %d, pid: %d)', - term.pty, term.fd, term.pid); - - return func(null, { - id: id, - pty: term.pty, - process: sanitize(conf.shell) - }); + return session.handleCreate(cols, rows, func); }); socket.on('data', function(id, data) { - if (!terms[id]) { - console.error('' - + 'Warning: Client attempting to' - + ' write to a non-existent terminal.' - + ' (id: %s)', id); - return; - } - terms[id].write(data); + return session.handleData(id, data); }); socket.on('kill', function(id) { - if (!terms[id]) return; - terms[id].destroy(); - delete terms[id]; + return session.handleKill(id); }); socket.on('resize', function(id, cols, rows) { - if (!terms[id]) return; - terms[id].resize(cols, rows); + return session.handleResize(id, cols, rows); }); socket.on('process', function(id, func) { - if (!terms[id]) return; - var name = terms[id].process; - return func(null, sanitize(name)); + return session.handleProcess(id, func); }); socket.on('disconnect', function() { - var key = Object.keys(terms) - , i = key.length - , term; - - while (i--) { - term = terms[key[i]]; - term.destroy(); - } - - if (state[req.user]) delete state[req.user]; - - console.log('Client disconnected. Killing all pty\'s...'); + //delete sessions[session.id]; + return session.handleDisconnect(); }); -}); - -/** - * Listen - */ - -app.listen(conf.port || 8080, conf.hostname); +}; -/** - * Basic Auth - */ +Server.prototype._basicAuth = function() { + var self = this; + var conf = this.conf; -function basicAuth() { if (!conf.users) { return function(req, res, next) { next(); @@ -262,19 +205,19 @@ function basicAuth() { var crypto = require('crypto') , saidWarning; - var sha1 = function(text) { + function sha1(text) { return crypto .createHash('sha1') .update(text) .digest('hex'); - }; + } - var hashed = function(hash) { + function hashed(hash) { if (!hash) return; return hash.length === 40 && !/[^a-f0-9]/.test(hash); - }; + } - var verify = function(user, pass, next) { + function verify(user, pass, next) { var user = sha1(user) , password; @@ -285,12 +228,12 @@ function basicAuth() { password = conf.users[user]; next(null, sha1(pass) === password); - }; + } // Hash everything for consistency. Object.keys(conf.users).forEach(function(name) { if (!saidWarning && !hashed(conf.users[name])) { - console.log('Warning: You should sha1 your usernames/passwords.'); + self.log('Warning: You should sha1 your usernames/passwords.'); saidWarning = true; } @@ -306,10 +249,207 @@ function basicAuth() { }); return express.basicAuth(verify); +}; + +Server.prototype.log = function() { + if (this.conf.log === false) return; + var args = Array.prototype.slice.call(arguments); + args[0] = 'tty.js: ' + args[0]; + console.log.apply(console, args); +}; + +Server.prototype.error = function() { + if (this.conf.log === false) return; + var args = Array.prototype.slice.call(arguments); + args[0] = 'tty.js: ' + args[0]; + console.error.apply(console, args); +}; + +Server.prototype._listen = express.createServer().listen; +Server.prototype.listen = function(port, hostname, func) { + return this._listen( + port || this.conf.port || 8080, + hostname || this.conf.hostname, + func); +}; + +/** + * Session + */ + +function Session(server, socket) { + this.server = server; + this.socket = socket; + this.terms = {}; + this.req = socket.handshake; + + var conf = this.server.conf; + var terms = this.terms; + var sessions = this.server.sessions; + var req = socket.handshake; + + this.id = req.user || Math.random() + ''; + + // Kill older session. + if (conf.sessions && conf.users) { + if (sessions[this.id]) { + try { + sessions[this.id].socket.disconnect(); + } catch (e) { + ; + } + } + sessions[this.id] = session; + } } +Session.prototype.log = function() { + var args = Array.prototype.slice.call(arguments); + args[0] = 'Session [' + this.id + ']: ' + args[0]; + return this.server.log.apply(this.server, arguments); +}; + +Session.prototype.error = function() { + var args = Array.prototype.slice.call(arguments); + args[0] = 'Session [' + this.id + ']: ' + args[0]; + return this.server.log.apply(this.server, arguments); +}; + +Session.prototype.handleCreate = function(cols, rows, func) { + var self = this; + var terms = this.terms; + var conf = this.server.conf; + var socket = this.socket; + + var len = Object.keys(terms).length + , term + , id; + + if (len >= conf.limitPerUser || pty.total >= conf.limitGlobal) { + return func('Terminal limit.'); + } + + term = pty.fork(conf.shell, conf.shellArgs, { + name: conf.termName, + cols: cols, + rows: rows, + cwd: conf.cwd || process.env.HOME + }); + + id = term.pty; + terms[id] = term; + + term.on('data', function(data) { + socket.emit('data', id, data); + }); + + term.on('close', function() { + // make sure it closes + // on the clientside + socket.emit('kill', id); + + // ensure removal + if (terms[id]) delete terms[id]; + + self.log( + 'Closed pty (%s): %d.', + term.pty, term.fd); + }); + + this.log('' + + 'Created shell with pty (%s) master/slave' + + ' pair (master: %d, pid: %d)', + term.pty, term.fd, term.pid); + + return func(null, { + id: id, + pty: term.pty, + process: sanitize(conf.shell) + }); +}; + +Session.prototype.handleData = function(id, data) { + var terms = this.terms; + if (!terms[id]) { + this.error('' + + 'Warning: Client attempting to' + + ' write to a non-existent terminal.' + + ' (id: %s)', id); + return; + } + terms[id].write(data); +}; + +Session.prototype.handleKill = function(id) { + var terms = this.terms; + if (!terms[id]) return; + terms[id].destroy(); + delete terms[id]; +}; + +Session.prototype.handleResize = function(id, cols, rows) { + var terms = this.terms; + if (!terms[id]) return; + terms[id].resize(cols, rows); +}; + +Session.prototype.handleProcess = function(id, func) { + var terms = this.terms; + if (!terms[id]) return; + var name = terms[id].process; + return func(null, sanitize(name)); +}; + +Session.prototype.handleDisconnect = function() { + var terms = this.terms; + var req = this.req; + var sessions = this.server.sessions; + + var key = Object.keys(terms) + , i = key.length + , term; + + while (i--) { + term = terms[key[i]]; + term.destroy(); + } + + if (sessions[req.user]) delete sessions[this.id]; + + this.log('Client disconnected. Killing all pty\'s...'); +}; + +/** + * Helpers + */ + function sanitize(file) { if (!file) return ''; file = file.split(' ')[0] || ''; return path.basename(file) || ''; } + +function applyConfig() { + for (var key in Terminal.options) { + if (!Object.prototype.hasOwnProperty.call(Terminal.options, key)) continue; + if (key === 'colors') { + var l = Terminal.options.colors.length + , i = 0; + + for (; i < l; i++) { + Terminal.colors[i] = Terminal.options.colors[i]; + } + } else { + Terminal[key] = Terminal.options[key]; + } + } + delete Terminal.options; +} + +/** + * Expose + */ + +exports = Server; +exports.config = config; +module.exports = exports; diff --git a/static/tty.js b/static/tty.js index 41508ad0..6db30f2d 100644 --- a/static/tty.js +++ b/static/tty.js @@ -80,9 +80,7 @@ tty.open = function() { if (lights) { on(lights, 'click', function() { - root.className = !root.className - ? 'dark' - : ''; + tty.toggleLights(); }); } @@ -116,7 +114,7 @@ tty.open = function() { }, 2 * 1000); // Keep windows maximized. - on(window, 'resize', function(ev) { + on(window, 'resize', function() { var i = tty.windows.length , win; @@ -149,6 +147,16 @@ tty.reset = function() { tty.emit('reset'); }; +/** + * Lights + */ + +tty.toggleLights = function() { + root.className = !root.className + ? 'dark' + : ''; +}; + /** * Window */ @@ -255,11 +263,14 @@ Window.prototype.bind = function() { }; Window.prototype.focus = function() { + // Restack var parent = this.element.parentNode; if (parent) { parent.removeChild(this.element); parent.appendChild(this.element); } + + // Focus Foreground Tab this.focused.focus(); tty.emit('focus window', this); @@ -309,7 +320,7 @@ Window.prototype.drag = function(ev) { (drag.top + ev.pageY - drag.pageY) + 'px'; } - function up(ev) { + function up() { el.style.opacity = ''; el.style.cursor = ''; root.style.cursor = ''; @@ -317,8 +328,13 @@ Window.prototype.drag = function(ev) { off(document, 'mousemove', move); off(document, 'mouseup', up); - tty.emit('drag window', self, el.style.left, el.style.top); - self.emit('drag', el.style.left, el.style.top); + var ev = { + left: el.style.left.replace(/\w+/g, ''), + top: el.style.top.replace(/\w+/g, '') + }; + + tty.emit('drag window', self, ev); + self.emit('drag', ev); } on(document, 'mousemove', move); @@ -352,7 +368,7 @@ Window.prototype.resizing = function(ev) { el.style.height = y + 'px'; } - function up(ev) { + function up() { var x, y; x = el.clientWidth / resize.w; @@ -441,9 +457,11 @@ Window.prototype.maximize = function() { Window.prototype.resize = function(cols, rows) { this.cols = cols; this.rows = rows; + this.each(function(term) { term.resize(cols, rows); }); + tty.emit('resize window', this, cols, rows); this.emit('resize', cols, rows); }; @@ -461,10 +479,12 @@ Window.prototype.createTab = function() { Window.prototype.highlight = function() { var self = this; + this.element.style.borderColor = 'orange'; setTimeout(function() { self.element.style.borderColor = ''; }, 200); + this.focus(); }; @@ -502,7 +522,6 @@ function Tab(win, socket) { var cols = win.cols , rows = win.rows; - // TODO: make this an EventEmitter Terminal.call(this, cols, rows); var button = document.createElement('div'); @@ -681,6 +700,7 @@ Tab.prototype.hookKeys = function() { // Alt-` to quickly swap between windows. if (key === '\x1b`') { var i = indexOf(tty.windows, this.window) + 1; + this._ignoreNext(); if (tty.windows[i]) return tty.windows[i].highlight(); if (tty.windows[0]) return tty.windows[0].highlight(); @@ -747,6 +767,7 @@ Tab.prototype._ignoreNext = function() { */ Tab.prototype._bindMouse = Tab.prototype.bindMouse; + Tab.prototype.bindMouse = function() { if (!Terminal.programFeatures) return this._bindMouse(); @@ -788,15 +809,22 @@ Tab.prototype.bindMouse = function() { Tab.prototype.pollProcessName = function(func) { var self = this; this.socket.emit('process', this.id, function(err, name) { - if (!err) self.setProcessName(name); - if (func) func(err, name); + if (err) return func && func(err); + self.setProcessName(name); + return func && func(null, name); }); }; Tab.prototype.setProcessName = function(name) { name = sanitize(name); + + if (this.process !== name) { + this.emit('process', name); + } + this.process = name; this.button.title = name; + if (this.window.focused === this) { // if (this.title) { // name += ' (' + this.title + ')'; From db646327a4af3811a519945f64c1a1946f6b78d7 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 13 Jun 2012 17:22:44 -0500 Subject: [PATCH 058/131] fully implement charset switching. fixes elinks bug. closes #28. --- static/term.js | 83 ++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 74 insertions(+), 9 deletions(-) diff --git a/static/term.js b/static/term.js index 202eac55..42854278 100644 --- a/static/term.js +++ b/static/term.js @@ -128,9 +128,14 @@ function Terminal(cols, rows, handler) { this.originMode = false; this.insertMode = false; this.wraparoundMode = false; - this.charset = null; this.normal = null; + // charset + this.charset = null; + this.gcharset = null; + this.glevel = 0; + this.charsets = [null]; + // mouse properties this.decLocator; this.x10Mouse; @@ -999,12 +1004,16 @@ Terminal.prototype.write = function(data) { break; // shift out - // case '\x0e': - // break; + case '\x0e': + this.glevel = 1; + this.charset = this.charsets[1]; + break; // shift in - // case '\x0f': - // break; + case '\x0f': + this.glevel = 0; + this.charset = this.charsets[0]; + break; // '\e' case '\x1b': @@ -1088,6 +1097,8 @@ Terminal.prototype.write = function(data) { // @ = default, G = utf-8 case '%': this.charset = null; + //this.glevel = 0; + //this.charsets[0] = null; this.state = normal; i++; break; @@ -1099,6 +1110,26 @@ Terminal.prototype.write = function(data) { case '+': case '-': case '.': + switch (ch) { + case '(': + this.gcharset = 0; + break; + case ')': + this.gcharset = 1; + break; + case '*': + this.gcharset = 2; + break; + case '+': + this.gcharset = 3; + break; + case '-': + this.gcharset = 1; + break; + case '.': + this.gcharset = 2; + break; + } this.state = charset; break; @@ -1106,7 +1137,12 @@ Terminal.prototype.write = function(data) { // A = ISO Latin-1 Supplemental. // Not implemented. case '/': - this.charset = null; + this.gcharset = 3; + this.charsets[this.gcharset] = null; + if (this.glevel === this.gcharset) { + this.charset = null; + } + this.gcharset = null; this.state = normal; i++; break; @@ -1159,12 +1195,20 @@ Terminal.prototype.write = function(data) { switch (ch) { // DEC Special Character and Line Drawing Set. case '0': - this.charset = SCLD; + this.charsets[this.gcharset] = Terminal.charsets.SCLD; + if (this.gcharset === this.glevel) { + this.charset = Terminal.charsets.SCLD; + } + this.gcharset = null; break; // United States (USASCII). case 'B': default: - this.charset = null; + this.charsets[this.gcharset] = null; + if (this.gcharset === this.glevel) { + this.charset = null; + } + this.gcharset = null; break; } this.state = normal; @@ -3088,6 +3132,10 @@ Terminal.prototype.setMode = function(params) { scrollTop: this.scrollTop, scrollBottom: this.scrollBottom, tabs: this.tabs + // XXX save charset(s) here? + // charset: this.charset, + // glevel: this.glevel, + // charsets: this.charsets }; this.reset(); this.normal = normal; @@ -3461,6 +3509,7 @@ Terminal.prototype.softReset = function(params) { this.curAttr = this.defAttr; this.x = this.y = 0; // ? this.charset = null; + this.charsets = [null]; // ?? }; // CSI Ps$ p @@ -3894,6 +3943,8 @@ Terminal.prototype.deleteColumns = function() { * Character Sets */ +Terminal.charsets = {}; + // DEC Special Character and Line Drawing Set. // http://vt100.net/docs/vt102-ug/table5-13.html // A lot of curses apps use this if they see TERM=xterm. @@ -3902,7 +3953,7 @@ Terminal.prototype.deleteColumns = function() { // reference above. xterm seems in line with the reference // when running vttest however. // The table below now uses xterm's output from vttest. -var SCLD = { +Terminal.charsets.SCLD = { // (0 '`': '\u25c6', // '◆' 'a': '\u2592', // '▒' 'b': '\u0009', // '\t' @@ -3936,6 +3987,20 @@ var SCLD = { '~': '\u00b7' // '·' }; +Terminal.charsets.UK = null; // (A +Terminal.charsets.US = null; // (B (USASCII) +Terminal.charsets.Dutch = null; // (4 +Terminal.charsets.Finnish = null; // (C or (5 +Terminal.charsets.French = null; // (R +Terminal.charsets.FrenchCanadian = null; // (Q +Terminal.charsets.German = null; // (K +Terminal.charsets.Italian = null; // (Y +Terminal.charsets.NorwegianDanish = null; // (E or (6 +Terminal.charsets.Spanish = null; // (Z +Terminal.charsets.Swedish = null; // (H or (7 +Terminal.charsets.Swiss = null; // (= +Terminal.charsets.ISOLatin = null; // /A + /** * Helpers */ From 2a6668379c3220a7c351439c1d53fe07ca9387cd Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 15 Jun 2012 09:10:25 -0500 Subject: [PATCH 059/131] add glevel methods and eventemitter calls. --- static/term.js | 14 ++++++++++++++ static/tty.js | 2 ++ 2 files changed, 16 insertions(+) diff --git a/static/term.js b/static/term.js index 42854278..0a140bed 100644 --- a/static/term.js +++ b/static/term.js @@ -105,6 +105,8 @@ var normal = 0 */ function Terminal(cols, rows, handler) { + EventEmitter.call(this); + this.cols = cols || Terminal.geometry[0]; this.rows = rows || Terminal.geometry[1]; @@ -2057,6 +2059,18 @@ Terminal.prototype.keyDown = function(ev) { return true; }; +Terminal.prototype.setgLevel = function(g) { + this.glevel = g; + this.charset = this.charsets[g]; +}; + +Terminal.prototype.setgCharset = function(g, charset) { + this.charsets[g] = charset; + if (this.glevel === g) { + this.charset = charset; + } +}; + Terminal.prototype.keyPress = function(ev) { var key; diff --git a/static/tty.js b/static/tty.js index 6db30f2d..f8f81a4c 100644 --- a/static/tty.js +++ b/static/tty.js @@ -164,6 +164,8 @@ tty.toggleLights = function() { function Window(socket) { var self = this; + EventEmitter.call(this); + var el , grip , bar From 247b452555666e9617fdd87dcc3f680eb9e8c8d0 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 15 Jun 2012 09:43:39 -0500 Subject: [PATCH 060/131] use different inheritence, futureproof for express. --- lib/config.js | 8 +++++++- lib/tty.js | 46 ++++++++++++++++++++++++++++++---------------- 2 files changed, 37 insertions(+), 17 deletions(-) diff --git a/lib/config.js b/lib/config.js index 57bbc248..bd8dbc57 100644 --- a/lib/config.js +++ b/lib/config.js @@ -94,12 +94,18 @@ function readConfig(file) { } function checkConfig(conf) { - conf = conf || {}; + conf = clone(conf || {}); if (typeof conf === 'string') { return readConfig(conf); } + if (conf.config) { + var file = conf.config; + delete conf.config; + merge(conf, readConfig(file)); + } + if (conf.__check) return conf; // flag diff --git a/lib/tty.js b/lib/tty.js index 61711353..2218d799 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -15,6 +15,8 @@ var express = require('express') , io = require('socket.io') , pty = require('pty.js'); +var proto = express.application || express.HTTPServer.prototype; + var config = require('./config'); /** @@ -22,35 +24,45 @@ var config = require('./config'); */ function Server(conf) { + if (!(this instanceof Server)) { + return new Server(conf); + } + conf = config.checkConfig(conf); - var self = conf.https && conf.https.key + var self = this; + var app = conf.https && conf.https.key ? express.createServer(conf.https) : express.createServer(); - // We can't inherit from express - // javascript-style, so we need to - // do it this way... - Object.keys(Server.prototype).forEach(function(key) { - self[key] = Server.prototype[key]; + // Inherit from express >= 3.0. + Object.getOwnPropertyNames(app).forEach(function(key) { + // A function here that has the original app + // closure-scoped would be a problem. + Object.defineProperty(self, key, Object.getOwnPropertyDescriptor(app, key)); }); - self.sessions = {}; - self.conf = conf; - self._auth = self._basicAuth(); - self.io = io.listen(self, { + // Call init again for good measure, + // to potentially get rid of + // closure-scoped references + // to the original app. + this.init(); + + this.sessions = {}; + this.conf = conf; + this._auth = this._basicAuth(); + this.io = io.listen(this, { log: false }); - self.init(); - - return self; + this._init(); } -Server.createServer = Server; +Server.prototype.__proto__ = express.createServer().__proto__; +// Server.prototype.__proto__ = proto; -Server.prototype.init = function() { - this.init = function() {}; +Server.prototype._init = function() { + this._init = function() {}; this.initMiddleware(); this.initRoutes(); this.initIO(); @@ -451,5 +463,7 @@ function applyConfig() { */ exports = Server; +exports.createServer = Server; exports.config = config; + module.exports = exports; From d7b14c8bbc17e0cfa2a2ac8cda81226d643cc8ea Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 19 Jun 2012 09:21:36 -0500 Subject: [PATCH 061/131] refactor, remove hooks, config. --- README.md | 39 ++++++---- lib/config.js | 39 ++++++---- lib/logger.js | 57 +++++++++++++++ lib/tty.js | 195 +++++++++++++++++++++++++++++++------------------ static/term.js | 39 +++++++--- static/tty.js | 19 +++++ 6 files changed, 279 insertions(+), 109 deletions(-) create mode 100644 lib/logger.js diff --git a/README.md b/README.md index 14e030de..4f8a90c8 100644 --- a/README.md +++ b/README.md @@ -31,6 +31,28 @@ Bellard's vt100 for [jslinux](http://bellard.org/jslinux/). $ npm install tty.js ``` +## Usage + +tty.js is an app, but it's also possible to hook into it programatically + +``` js +var tty = require('tty.js'); + +var app = tty.createServer({ + shell: 'bash', + users: { + foo: 'bar' + }, + port: 8000 +}); + +app.get('/foo', function(req, res, next) { + res.send('bar'); +}); + +app.listen(); +``` + ## Configuration Configuration is stored in `~/.tty.js/config.json` or `~/.tty.js` as a single @@ -52,8 +74,10 @@ JSON file. An example configuration file looks like: "static": "./static", "limitGlobal": 10000, "limitPerUser": 1000, - "hooks": "./hooks.js", "cwd": ".", + "syncSession": true, + "sessionTimeout": 600000, + "log": true, "term": { "termName": "xterm", "geometry": [80, 30], @@ -91,19 +115,6 @@ Usernames and passwords can be plaintext or sha1 hashes. If tty.js fails to check your terminfo properly, you can force your `TERM` to `xterm-256color` by setting `"termName": "xterm-256color"` in your config. -### Example Hooks File - -``` js -var db = require('./db'); - -module.exports = { - auth: function(user, pass, next) { - // Do database auth - next(null, pass === password); - } -}; -``` - ## Security tty.js currently has https as an option. It also has express' default basic diff --git a/lib/config.js b/lib/config.js index bd8dbc57..2111857b 100644 --- a/lib/config.js +++ b/lib/config.js @@ -4,7 +4,8 @@ */ var path = require('path') - , fs = require('fs'); + , fs = require('fs') + , logger = require('./logger'); /** * Default Config @@ -23,7 +24,9 @@ var schema = { // static: './static', // limitGlobal: 10000, // limitPerUser: 1000, - // hooks: './hooks.js', + // syncSession: true, + // sessionTimeout: 10 * 60 * 1000, + // log: true, // cwd: '.', term: { // termName: 'xterm', @@ -157,9 +160,11 @@ function checkConfig(conf) { conf.term.termName = conf.termName || conf.term.termName; if (!conf.term.termName) { // tput -Txterm-256color longname - conf.term.termName = exists('/usr/share/terminfo/x/xterm+256color') - ? 'xterm-256color' - : 'xterm'; + conf.term.termName = + exists('/usr/share/terminfo/x/xterm+256color') + || exists('/usr/share/terminfo/x/xterm-256color') + ? 'xterm-256color' + : 'xterm'; } conf.termName = conf.term.termName; @@ -172,9 +177,6 @@ function checkConfig(conf) { delete conf.users; } - // hooks - conf.hooks = conf.hooks || tryRequire(dir, 'hooks.js'); - // cwd if (conf.cwd) { conf.cwd = path.resolve(dir, conf.cwd); @@ -194,7 +196,7 @@ function checkLegacy(conf) { if (conf.auth && conf.auth.username && !conf.auth.disabled) { conf.users[conf.auth.username] = conf.auth.password; } - console.error('`auth` is deprecated, please use `users` instead.'); + logger('error', '`auth` is deprecated, please use `users` instead.'); } if (conf.https && conf.https.key) { @@ -227,13 +229,16 @@ function checkLegacy(conf) { } if (conf.hooks) { - conf.hooks = tryRequire(conf.dir, conf.hooks); + out.push('' + + '`hooks` is deprecated, please programmatically ' + + 'hook into your tty.js server instead.'); } if (out.length) { - out = out.join('\n'); - console.error(out); - console.error('Exiting...'); + out.forEach(function(out) { + logger('error', out); + }); + logger('error', 'Exiting.'); process.exit(1); } } @@ -297,12 +302,18 @@ function parseArg() { function getarg() { var arg = argv.shift(); - if (arg && arg.indexOf('--') === 0) { + if (!arg) return arg; + if (arg.indexOf('--') === 0) { arg = arg.split('='); if (arg.length > 1) { argv.unshift(arg.slice(1).join('=')); } return arg[0]; + } else if (arg[0] === '-' && arg.length > 2) { + argv = arg.substring(1).split('').map(function(ch) { + return '-' + ch; + }).concat(argv); + arg = argv.shift(); } return arg; } diff --git a/lib/logger.js b/lib/logger.js new file mode 100644 index 00000000..bf9b2c62 --- /dev/null +++ b/lib/logger.js @@ -0,0 +1,57 @@ +/** + * tty.js: logger.js + * Copyright (c) 2012, Christopher Jeffrey (MIT License) + */ + +/** + * Logger + */ + +var slice = Array.prototype.slice; + +var isatty = require('tty').isatty; +isatty = [isatty(0), isatty(1), isatty(2)]; + +var levels = { + 'log': [34, 'log'], + 'error': [41, 'error'], + 'warning': [31, 'error'] // 31, 33, 91 +}; + +function logger(level) { + var args = Array.prototype.slice.call(arguments, 1); + + if (typeof args[0] !== 'string') args.unshift(''); + + level = levels[level]; + + args[0] = '\x1b[' + + level[0] + + 'm[' + + logger.prefix + + ']\x1b[m ' + + args[0]; + + if ((level[1] === 'log' && !isatty[1]) + || (level[1] === 'error' && !isatty[2])) { + args[0] = args[0].replace(/\x1b\[(?:\d+(?:;\d+)*)?m/g, ''); + } + + return console[level[1]].apply(console, args); +} + +logger.prefix = 'tty.js'; + +logger.log = function() { + return logger('log', slice.call(arguments)); +}; + +logger.warning = function() { + return logger('warning', slice.call(arguments)); +}; + +logger.error = function() { + return logger('error', slice.call(arguments)); +}; + +module.exports = logger; diff --git a/lib/tty.js b/lib/tty.js index 2218d799..1081789f 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -9,15 +9,14 @@ var path = require('path') , fs = require('fs') - , EventEmitter = require('events').EventEmitter; + , Stream = require('stream').Stream; var express = require('express') , io = require('socket.io') , pty = require('pty.js'); -var proto = express.application || express.HTTPServer.prototype; - -var config = require('./config'); +var config = require('./config') + , logger = require('./logger'); /** * Server @@ -56,10 +55,13 @@ function Server(conf) { }); this._init(); + + this.on('listening', function() { + self.log('Listening on port %s.', self.conf.port); + }); } Server.prototype.__proto__ = express.createServer().__proto__; -// Server.prototype.__proto__ = proto; Server.prototype._init = function() { this._init = function() {}; @@ -69,6 +71,7 @@ Server.prototype._init = function() { }; Server.prototype.initMiddleware = function() { + var self = this; var conf = this.conf; this.use(function(req, res, next) { @@ -85,18 +88,14 @@ Server.prototype.initMiddleware = function() { next(); }); - this.use(this._auth); + this.use(function(req, res, next) { + return self._auth(req, res, next); + }); if (conf.static) { this.use(express.static(conf.static)); } - // var icon = conf.static + '/favicon.ico'; - // if (!conf.static || !path.existsSync(icon)) { - // icon = __dirname + '/../static/favicon.ico'; - // } - // this.use(express.favicon(icon)); - // If there is a custom favicon in the custom // static directory, this will be ignored. this.use(express.favicon(__dirname + '/../static/favicon.ico')); @@ -106,6 +105,10 @@ Server.prototype.initMiddleware = function() { this.use(express.static(__dirname + '/../static')); }; +Server.prototype.setAuth = function(func) { + this._auth = func; +}; + Server.prototype.initRoutes = function() { var self = this; this.get('/options.js', function(req, res, next) { @@ -159,7 +162,7 @@ Server.prototype.initIO = function() { Server.prototype.handleAuth = function(data, next) { var io = this.io; - data.__proto__ = EventEmitter.prototype; + data.__proto__ = Stream.prototype; this._auth(data, null, function(err) { data.user = data.remoteUser || data.user; return !err @@ -171,9 +174,6 @@ Server.prototype.handleAuth = function(data, next) { Server.prototype.handleConnection = function(socket) { var session = new Session(this, socket); - //this.sessions[session.id] = session; - //this.sessions.push(session); - socket.on('create', function(cols, rows, func) { return session.handleCreate(cols, rows, func); }); @@ -195,7 +195,6 @@ Server.prototype.handleConnection = function(socket) { }); socket.on('disconnect', function() { - //delete sessions[session.id]; return session.handleDisconnect(); }); }; @@ -210,11 +209,9 @@ Server.prototype._basicAuth = function() { }; } - if (conf.hooks && conf.hooks.auth) { - return express.basicAuth(conf.hooks.auth); - } - var crypto = require('crypto') + , users = conf.users + , hashedUsers = {} , saidWarning; function sha1(text) { @@ -230,22 +227,24 @@ Server.prototype._basicAuth = function() { } function verify(user, pass, next) { - var user = sha1(user) + var username = sha1(user) , password; - if (!Object.hasOwnProperty.call(conf.users, user)) { + if (!Object.hasOwnProperty.call(hashedUsers, username)) { return next(); } - password = conf.users[user]; + password = hashedUsers[username]; + + if (sha1(pass) !== password) return next(true); - next(null, sha1(pass) === password); + next(null, user); } // Hash everything for consistency. - Object.keys(conf.users).forEach(function(name) { - if (!saidWarning && !hashed(conf.users[name])) { - self.log('Warning: You should sha1 your usernames/passwords.'); + Object.keys(users).forEach(function(name) { + if (!saidWarning && !hashed(users[name])) { + self.warning('You should sha1 your user information.'); saidWarning = true; } @@ -253,36 +252,37 @@ Server.prototype._basicAuth = function() { ? sha1(name) : name; - conf.users[username] = !hashed(conf.users[name]) - ? sha1(conf.users[name]) - : conf.users[name]; - - if (username !== name) delete conf.users[name]; + hashedUsers[username] = !hashed(users[name]) + ? sha1(users[name]) + : users[name]; }); return express.basicAuth(verify); }; Server.prototype.log = function() { - if (this.conf.log === false) return; - var args = Array.prototype.slice.call(arguments); - args[0] = 'tty.js: ' + args[0]; - console.log.apply(console, args); + return this._log('log', slice.call(arguments)); }; Server.prototype.error = function() { + return this._log('error', slice.call(arguments)); +}; + +Server.prototype.warning = function() { + return this._log('warning', slice.call(arguments)); +}; + +Server.prototype._log = function(level, args) { if (this.conf.log === false) return; - var args = Array.prototype.slice.call(arguments); - args[0] = 'tty.js: ' + args[0]; - console.error.apply(console, args); + args.unshift(level); + return logger.apply(null, args); }; Server.prototype._listen = express.createServer().listen; Server.prototype.listen = function(port, hostname, func) { - return this._listen( - port || this.conf.port || 8080, - hostname || this.conf.hostname, - func); + port = port || this.conf.port || 8080; + hostname = hostname || this.conf.hostname; + return this._listen(port, hostname, func); }; /** @@ -300,31 +300,64 @@ function Session(server, socket) { var sessions = this.server.sessions; var req = socket.handshake; - this.id = req.user || Math.random() + ''; + this.user = req.user; + this.id = req.user || this.uid(); - // Kill older session. - if (conf.sessions && conf.users) { + // Kill/sync older session. + if (conf.syncSession) { if (sessions[this.id]) { - try { - sessions[this.id].socket.disconnect(); - } catch (e) { - ; - } + this.sync(sessions[this.id].terms); + sessions[this.id].destroy(); } - sessions[this.id] = session; } + + sessions[this.id] = this; + + this.log('Session \x1b[1m%s\x1b[m created.', this.id); } +Session.uid = 0; +Session.prototype.uid = function() { + if (this.server.conf.syncSession) { + var req = this.req; + return req.address.address + + ':' + req.address.port + + ':' + req.headers['user-agent']; + } + return Session.uid++ + ''; +}; + +Session.prototype.destroy = function() { + try { + this.socket.disconnect(); + } catch (e) { + ; + } +}; + Session.prototype.log = function() { - var args = Array.prototype.slice.call(arguments); - args[0] = 'Session [' + this.id + ']: ' + args[0]; - return this.server.log.apply(this.server, arguments); + return this._log('log', slice.call(arguments)); }; Session.prototype.error = function() { - var args = Array.prototype.slice.call(arguments); - args[0] = 'Session [' + this.id + ']: ' + args[0]; - return this.server.log.apply(this.server, arguments); + return this._log('error', slice.call(arguments)); +}; + +Session.prototype.warning = function() { + return this._log('warning', slice.call(arguments)); +}; + +Session.prototype._log = function(level, args) { + if (typeof args[0] !== 'string') args.unshift(''); + var id = this.id.split(':')[0]; + //args[0] = '\x1b[35m(' + id + ')\x1b[m ' + args[0]; + args[0] = '\x1b[1m' + id + '\x1b[m ' + args[0]; + return this.server._log(level, args); +}; + +Session.prototype.sync = function(terms) { + if (terms) this.terms = terms; + this.socket.emit('sync', this.terms); }; Session.prototype.handleCreate = function(cols, rows, func) { @@ -338,7 +371,8 @@ Session.prototype.handleCreate = function(cols, rows, func) { , id; if (len >= conf.limitPerUser || pty.total >= conf.limitGlobal) { - return func('Terminal limit.'); + this.warning('Terminal limit reached.'); + return func({ error: 'Terminal limit.' }); } term = pty.fork(conf.shell, conf.shellArgs, { @@ -356,11 +390,11 @@ Session.prototype.handleCreate = function(cols, rows, func) { }); term.on('close', function() { - // make sure it closes - // on the clientside + // Make sure it closes + // on the clientside. socket.emit('kill', id); - // ensure removal + // Ensure removal. if (terms[id]) delete terms[id]; self.log( @@ -368,10 +402,9 @@ Session.prototype.handleCreate = function(cols, rows, func) { term.pty, term.fd); }); - this.log('' - + 'Created shell with pty (%s) master/slave' - + ' pair (master: %d, pid: %d)', - term.pty, term.fd, term.pid); + this.log( + 'Created pty (id: %s, master: %d, pid: %d).', + id, term.fd, term.pid); return func(null, { id: id, @@ -383,8 +416,8 @@ Session.prototype.handleCreate = function(cols, rows, func) { Session.prototype.handleData = function(id, data) { var terms = this.terms; if (!terms[id]) { - this.error('' - + 'Warning: Client attempting to' + this.warning('' + + 'Client attempting to' + ' write to a non-existent terminal.' + ' (id: %s)', id); return; @@ -413,6 +446,7 @@ Session.prototype.handleProcess = function(id, func) { }; Session.prototype.handleDisconnect = function() { + var self = this; var terms = this.terms; var req = this.req; var sessions = this.server.sessions; @@ -426,15 +460,29 @@ Session.prototype.handleDisconnect = function() { term.destroy(); } - if (sessions[req.user]) delete sessions[this.id]; + if (!this.server.conf.syncSession) { + if (sessions[req.user]) { + delete sessions[this.id]; + } + } else { + // XXX This could be done differently. + var timeout = this.server.conf.sessionTimeout || 10 * 60 * 1000; + setTimeout(function() { + if (sessions[req.user]) { + delete sessions[self.id]; + } + }, timeout); + } - this.log('Client disconnected. Killing all pty\'s...'); + this.log('Client disconnected. Killing all pty\'s.'); }; /** * Helpers */ +var slice = Array.prototype.slice; + function sanitize(file) { if (!file) return ''; file = file.split(' ')[0] || ''; @@ -463,7 +511,10 @@ function applyConfig() { */ exports = Server; -exports.createServer = Server; +exports.Server = Server; +exports.Session = Session; exports.config = config; +exports.logger = logger; +exports.createServer = Server; module.exports = exports; diff --git a/static/term.js b/static/term.js index 0a140bed..de63dd08 100644 --- a/static/term.js +++ b/static/term.js @@ -41,10 +41,11 @@ var window = this * EventEmitter */ -function EventEmitter() {} +function EventEmitter() { + this._events = {}; +} EventEmitter.prototype.addListener = function(type, listener) { - this._events = this._events || {}; this._events[type] = this._events[type] || []; this._events[type].push(listener); }; @@ -52,13 +53,13 @@ EventEmitter.prototype.addListener = function(type, listener) { EventEmitter.prototype.on = EventEmitter.prototype.addListener; EventEmitter.prototype.removeListener = function(type, listener) { - if (!this._events || !this._events[type]) return; + if (!this._events[type]) return; var obj = this._events[type] , i = obj.length; while (i--) { - if (obj[i] === listener) { + if (obj[i] === listener || obj[i].listener === listener) { obj.splice(i, 1); return; } @@ -67,16 +68,22 @@ EventEmitter.prototype.removeListener = function(type, listener) { EventEmitter.prototype.off = EventEmitter.prototype.removeListener; +EventEmitter.prototype.removeAllListeners = function(type) { + if (this._events[type]) delete this._events[type]; +}; + EventEmitter.prototype.once = function(type, listener) { - this.on(type, function on() { + function on() { var args = Array.prototype.slice.call(arguments); this.removeListener(type, on); return listener.apply(this, args); - }); + } + on.listener = listener; + return this.on(type, on); }; EventEmitter.prototype.emit = function(type) { - if (!this._events || !this._events[type]) return; + if (!this._events[type]) return; var args = Array.prototype.slice.call(arguments, 1) , obj = this._events[type] @@ -88,6 +95,10 @@ EventEmitter.prototype.emit = function(type) { } }; +EventEmitter.prototype.listeners = function(type) { + return this._events[type] = this._events[type] || []; +}; + /** * States */ @@ -107,11 +118,21 @@ var normal = 0 function Terminal(cols, rows, handler) { EventEmitter.call(this); + var options; + if (typeof cols === 'object') { + options = cols; + cols = options.cols; + rows = options.rows; + handler = options.handler; + } + this._options = options || {}; + this.cols = cols || Terminal.geometry[0]; this.rows = rows || Terminal.geometry[1]; - //if (handler) this.handler = handler; - if (handler) this.on('data', handler); + if (handler) { + this.on('data', handler); + } this.ybase = 0; this.ydisp = 0; diff --git a/static/tty.js b/static/tty.js index f8f81a4c..82418de2 100644 --- a/static/tty.js +++ b/static/tty.js @@ -100,6 +100,21 @@ tty.open = function() { tty.terms[id]._destroy(); }); + tty.socket.on('sync', function(terms) { + console.log('Attempting to sync...'); + console.log(terms); + tty.reset(); + terms.forEach(function(term) { + var emit = tty.socket.emit; + tty.socket.emit = function() {}; + var win = new Window; + Object.keys(term).forEach(function(key) { + win.tabs[0][key] = term[key]; + }); + tty.socket.emit = emit; + }); + }); + // We would need to poll the os on the serverside // anyway. there's really no clean way to do this. // This is just easier to do on the @@ -762,6 +777,10 @@ Tab.prototype._ignoreNext = function() { this.handler = function() { this.handler = handler; }; + var showCursor = this.showCursor; + this.showCursor = function() { + this.showCursor = showCursor; + }; }; /** From 36717df8e96f35f4e2bd3fd585e9361f1439fc7e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 19 Jun 2012 18:00:22 -0500 Subject: [PATCH 062/131] more robust charset management --- static/term.js | 130 ++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 101 insertions(+), 29 deletions(-) diff --git a/static/term.js b/static/term.js index de63dd08..94893343 100644 --- a/static/term.js +++ b/static/term.js @@ -969,6 +969,7 @@ Terminal.prototype.scrollDisp = function(disp) { Terminal.prototype.write = function(data) { var l = data.length , i = 0 + , cs , ch; this.refreshStart = this.y; @@ -1028,14 +1029,12 @@ Terminal.prototype.write = function(data) { // shift out case '\x0e': - this.glevel = 1; - this.charset = this.charsets[1]; + this.setgLevel(1); break; // shift in case '\x0f': - this.glevel = 0; - this.charset = this.charsets[0]; + this.setgLevel(0); break; // '\e' @@ -1119,9 +1118,9 @@ Terminal.prototype.write = function(data) { // ESC % Select default/utf-8 character set. // @ = default, G = utf-8 case '%': - this.charset = null; - //this.glevel = 0; - //this.charsets[0] = null; + //this.charset = null; + this.setgLevel(0); + this.setgCharset(0, Terminal.charsets.US); this.state = normal; i++; break; @@ -1161,13 +1160,44 @@ Terminal.prototype.write = function(data) { // Not implemented. case '/': this.gcharset = 3; - this.charsets[this.gcharset] = null; - if (this.glevel === this.gcharset) { - this.charset = null; - } - this.gcharset = null; - this.state = normal; - i++; + this.state = charset; + i--; + break; + + // ESC N + // Single Shift Select of G2 Character Set + // ( SS2 is 0x8e). This affects next character only. + case 'N': + break; + // ESC O + // Single Shift Select of G3 Character Set + // ( SS3 is 0x8f). This affects next character only. + case 'O': + break; + // ESC n + // Invoke the G2 Character Set as GL (LS2). + case 'n': + this.setgLevel(2); + break; + // ESC o + // Invoke the G3 Character Set as GL (LS3). + case 'o': + this.setgLevel(3); + break; + // ESC | + // Invoke the G3 Character Set as GR (LS3R). + case '|': + this.setgLevel(3); + break; + // ESC } + // Invoke the G2 Character Set as GR (LS2R). + case '}': + this.setgLevel(2); + break; + // ESC ~ + // Invoke the G1 Character Set as GR (LS1R). + case '~': + this.setgLevel(1); break; // ESC 7 Save Cursor (DECSC). @@ -1216,24 +1246,58 @@ Terminal.prototype.write = function(data) { case charset: switch (ch) { - // DEC Special Character and Line Drawing Set. - case '0': - this.charsets[this.gcharset] = Terminal.charsets.SCLD; - if (this.gcharset === this.glevel) { - this.charset = Terminal.charsets.SCLD; - } - this.gcharset = null; + case '0': // DEC Special Character and Line Drawing Set. + cs = Terminal.charsets.SCLD; break; - // United States (USASCII). - case 'B': - default: - this.charsets[this.gcharset] = null; - if (this.gcharset === this.glevel) { - this.charset = null; - } - this.gcharset = null; + case 'A': // UK + cs = Terminal.charsets.UK; + break; + case 'B': // United States (USASCII). + cs = Terminal.charsets.US; + break; + case '4': // Dutch + cs = Terminal.charsets.Dutch; + break; + case 'C': // Finnish + case '5': + cs = Terminal.charsets.Finnish; + break; + case 'R': // French + cs = Terminal.charsets.French; + break; + case 'Q': // FrenchCanadian + cs = Terminal.charsets.FrenchCanadian; + break; + case 'K': // German + cs = Terminal.charsets.German; + break; + case 'Y': // Italian + cs = Terminal.charsets.Italian; + break; + case 'E': // NorwegianDanish + case '6': + cs = Terminal.charsets.NorwegianDanish; + break; + case 'Z': // Spanish + cs = Terminal.charsets.Spanish; + break; + case 'H': // Swedish + case '7': + cs = Terminal.charsets.Swedish; + break; + case '=': // Swiss + cs = Terminal.charsets.Swiss; + break; + case '/': // ISOLatin (actually /A) + cs = Terminal.charsets.ISOLatin; + i++; + break; + default: // Default + cs = Terminal.charsets.US; break; } + this.setgCharset(this.gcharset, cs); + this.gcharset = null; this.state = normal; break; @@ -3096,6 +3160,13 @@ Terminal.prototype.setMode = function(params) { case 1: this.applicationKeypad = true; break; + case 2: + this.setgCharset(0, Terminal.charsets.US); + this.setgCharset(1, Terminal.charsets.US); + this.setgCharset(2, Terminal.charsets.US); + this.setgCharset(3, Terminal.charsets.US); + // set VT100 mode here + break; case 3: // 132 col mode this.savedCols = this.cols; this.resize(132, this.rows); @@ -3544,6 +3615,7 @@ Terminal.prototype.softReset = function(params) { this.curAttr = this.defAttr; this.x = this.y = 0; // ? this.charset = null; + this.glevel = 0; // ?? this.charsets = [null]; // ?? }; From 0f5aca8fa3bf4ffee224a252e7573852e116db72 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 21 Jun 2012 13:22:14 -0500 Subject: [PATCH 063/131] change logger and session management. --- lib/config.js | 45 +++++++++++++++++++++++------------- lib/logger.js | 38 ++++++++++++++++-------------- lib/tty.js | 64 +++++++++++++++++++++++++++++++++++---------------- 3 files changed, 94 insertions(+), 53 deletions(-) diff --git a/lib/config.js b/lib/config.js index 2111857b..c566257a 100644 --- a/lib/config.js +++ b/lib/config.js @@ -172,6 +172,11 @@ function checkConfig(conf) { conf.limitPerUser = conf.limitPerUser || Infinity; conf.limitGlobal = conf.limitGlobal || Infinity; + // session timeout + if (typeof conf.sessionTimeout !== 'number') { + conf.sessionTimeout = 10 * 60 * 1000; + } + // users if (conf.users && !Object.keys(conf.users).length) { delete conf.users; @@ -302,19 +307,29 @@ function parseArg() { function getarg() { var arg = argv.shift(); - if (!arg) return arg; + if (arg.indexOf('--') === 0) { + // e.g. --opt arg = arg.split('='); if (arg.length > 1) { + // e.g. --opt=val argv.unshift(arg.slice(1).join('=')); } - return arg[0]; - } else if (arg[0] === '-' && arg.length > 2) { - argv = arg.substring(1).split('').map(function(ch) { - return '-' + ch; - }).concat(argv); - arg = argv.shift(); + arg = arg[0]; + } else if (arg[0] === '-') { + if (arg.length > 2) { + // e.g. -abc + argv = arg.substring(1).split('').map(function(ch) { + return '-' + ch; + }).concat(argv); + arg = argv.shift(); + } else { + // e.g. -a + } + } else { + // e.g. foo } + return arg; } @@ -356,18 +371,16 @@ options = exports.options = parseArg(); */ function readResources() { - var home = process.env.HOME - , colors = [] + var colors = [] , defs = {} + , def + , color , text; - try { - text = fs.readFileSync(path.join(home, '.Xresources'), 'utf8'); - } catch(e) { - return colors; - } + text = tryRead(process.env.HOME, '.Xresources'); + if (!text) return colors; - var def = /#\s*define\s+((?:[^\s]|\\\s)+)\s+((?:[^\n]|\\\n)+)/g; + def = /#\s*define\s+((?:[^\s]|\\\s)+)\s+((?:[^\n]|\\\n)+)/g; text = text.replace(def, function(__, name, val) { name = name.replace(/\\\s/g, ''); defs[name] = val.replace(/\\\n/g, ''); @@ -378,7 +391,7 @@ function readResources() { return defs[name] || name; }); - var color = /(?:^|\n)[^\s]*(?:\*|\.)color(\d+):([^\n]+)/g; + color = /(?:^|\n)[^\s]*(?:\*|\.)color(\d+):([^\n]+)/g; text.replace(color, function(__, no, color) { if (!colors[no]) colors[no] = color.trim(); }); diff --git a/lib/logger.js b/lib/logger.js index bf9b2c62..3ea2aa46 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -3,27 +3,19 @@ * Copyright (c) 2012, Christopher Jeffrey (MIT License) */ +var slice = Array.prototype.slice + , isatty = require('tty').isatty; + /** * Logger */ -var slice = Array.prototype.slice; - -var isatty = require('tty').isatty; -isatty = [isatty(0), isatty(1), isatty(2)]; - -var levels = { - 'log': [34, 'log'], - 'error': [41, 'error'], - 'warning': [31, 'error'] // 31, 33, 91 -}; - function logger(level) { var args = Array.prototype.slice.call(arguments, 1); if (typeof args[0] !== 'string') args.unshift(''); - level = levels[level]; + level = logger.levels[level]; args[0] = '\x1b[' + level[0] @@ -32,26 +24,38 @@ function logger(level) { + ']\x1b[m ' + args[0]; - if ((level[1] === 'log' && !isatty[1]) - || (level[1] === 'error' && !isatty[2])) { + if ((level[1] === 'log' && !logger.isatty[1]) + || (level[1] === 'error' && !logger.isatty[2])) { args[0] = args[0].replace(/\x1b\[(?:\d+(?:;\d+)*)?m/g, ''); } return console[level[1]].apply(console, args); } +logger.isatty = [isatty(0), isatty(1), isatty(2)]; + +logger.levels = { + 'log': [34, 'log'], + 'error': [41, 'error'], + 'warning': [31, 'error'] // 31, 33, 91 +}; + logger.prefix = 'tty.js'; logger.log = function() { - return logger('log', slice.call(arguments)); + return logger.apply(null, ['log'].concat(slice.call(arguments))); }; logger.warning = function() { - return logger('warning', slice.call(arguments)); + return logger.apply(null, ['warning'].concat(slice.call(arguments))); }; logger.error = function() { - return logger('error', slice.call(arguments)); + return logger.apply(null, ['error'].concat(slice.call(arguments))); }; +/** + * Expose + */ + module.exports = logger; diff --git a/lib/tty.js b/lib/tty.js index 1081789f..e4590009 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -308,6 +308,7 @@ function Session(server, socket) { if (sessions[this.id]) { this.sync(sessions[this.id].terms); sessions[this.id].destroy(); + delete sessions[this.id]; } } @@ -333,6 +334,11 @@ Session.prototype.destroy = function() { } catch (e) { ; } + // This will keep the terms alive after + // the syncSession `if` clause in the constructor. + // Need to clear the timeout set by handleDisconnect. + // It's also necessary for clearing the timeout period. + this.clearTimeout(); }; Session.prototype.log = function() { @@ -350,7 +356,6 @@ Session.prototype.warning = function() { Session.prototype._log = function(level, args) { if (typeof args[0] !== 'string') args.unshift(''); var id = this.id.split(':')[0]; - //args[0] = '\x1b[35m(' + id + ')\x1b[m ' + args[0]; args[0] = '\x1b[1m' + id + '\x1b[m ' + args[0]; return this.server._log(level, args); }; @@ -448,33 +453,52 @@ Session.prototype.handleProcess = function(id, func) { Session.prototype.handleDisconnect = function() { var self = this; var terms = this.terms; - var req = this.req; var sessions = this.server.sessions; + var conf = this.server.conf; - var key = Object.keys(terms) - , i = key.length - , term; + this.log('Client disconnected.'); - while (i--) { - term = terms[key[i]]; - term.destroy(); + if (!conf.syncSession) { + destroy(); + } else { + // XXX This could be done differently. + this.setTimeout(conf.sessionTimeout, destroy); + this.log( + 'Preserving session for %d minutes.', + conf.sessionTimeout / 1000 / 60 | 0); } - if (!this.server.conf.syncSession) { - if (sessions[req.user]) { - delete sessions[this.id]; + // XXX Possibly create a second/different + // destroy function to accompany the one + // above? + function destroy() { + var key = Object.keys(terms) + , i = key.length + , term; + + while (i--) { + term = terms[key[i]]; + delete terms[key[i]]; + term.destroy(); } - } else { - // XXX This could be done differently. - var timeout = this.server.conf.sessionTimeout || 10 * 60 * 1000; - setTimeout(function() { - if (sessions[req.user]) { - delete sessions[self.id]; - } - }, timeout); + + if (sessions[self.id]) { + delete sessions[self.id]; + } + + self.log('Killing all pty\'s.'); } +}; + +Session.prototype.setTimeout = function(time, func) { + this.clearTimeout(); + this.timeout = setTimeout(func.bind(this), time); +}; - this.log('Client disconnected. Killing all pty\'s.'); +Session.prototype.clearTimeout = function() { + if (!this.timeout) return; + clearTimeout(this.timeout); + delete this.timeout; }; /** From bf2b19679814d8bf53a357083c06825a5c4123c2 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 24 Jun 2012 06:41:48 -0500 Subject: [PATCH 064/131] refactor config.js --- lib/config.js | 181 +++++++++++++++++++++----------------------------- lib/tty.js | 8 +-- 2 files changed, 79 insertions(+), 110 deletions(-) diff --git a/lib/config.js b/lib/config.js index c566257a..9c776989 100644 --- a/lib/config.js +++ b/lib/config.js @@ -8,40 +8,9 @@ var path = require('path') , logger = require('./logger'); /** - * Default Config + * Options */ -var schema = { - users: {}, - https: { - key: null, - cert: null - }, - port: 8080, - // hostname: '0.0.0.0', - // shell: 'sh', - // shellArgs: ['arg1', 'arg2'], - // static: './static', - // limitGlobal: 10000, - // limitPerUser: 1000, - // syncSession: true, - // sessionTimeout: 10 * 60 * 1000, - // log: true, - // cwd: '.', - term: { - // termName: 'xterm', - // geometry: [80, 30], - // visualBell: false, - // popOnBell: false, - // cursorBlink: true, - // scrollback: 1000, - // screenKeys: false, - // colors: [], - // programFeatures: false, - // debug: false - } -}; - var options; /** @@ -59,7 +28,7 @@ function readConfig(file) { dir = path.dirname(file); json = options.config; } else { - dir = path.join(home, '.tty.js'); + dir = process.env.TTYJS_PATH || path.join(home, '.tty.js'); json = path.join(dir, 'config.json'); } @@ -69,19 +38,12 @@ function readConfig(file) { dir = home; } - // read conf conf = JSON.parse(fs.readFileSync(json, 'utf8')); - - // ensure schema - ensure(conf, schema); } else { if (!exists(dir)) { fs.mkdirSync(dir, 0700); } - // ensure schema - ensure(conf, schema); - fs.writeFileSync(json, JSON.stringify(conf, null, 2)); fs.chmodSync(json, 0600); } @@ -109,84 +71,88 @@ function checkConfig(conf) { merge(conf, readConfig(file)); } - if (conf.__check) return conf; - // flag + if (conf.__check) return conf; conf.__check = true; - // maybe remove? - ensure(conf, schema); + // merge options + merge(conf, options.conf); - if (!conf.dir) { - conf.dir = process.env.HOME + '/.tty.js'; - //conf.dir = process.cwd(); - } + // directory and config file + conf.dir = conf.dir || ''; + conf.json = conf.json || ''; - if (!conf.json) { - conf.json = process.env.HOME + '/.tty.js/config.json'; - //conf.json = process.cwd() + '/config.json'; + // users + conf.users = conf.users || {}; + if (conf.auth && conf.auth.username && !conf.auth.disabled) { + conf.users[conf.auth.username] = conf.auth.password; } - var dir = conf.dir - , json = conf.json; - - // merge options - merge(conf, options); + // https + conf.https = conf.https || conf.ssl || conf.tls || {}; + conf.https = { + key: tryRead(conf.dir, conf.https.key || 'server.key') || conf.https.key, + cert: tryRead(conf.dir, conf.https.cert || 'server.crt') || conf.https.cert + }; - // check legacy features - checkLegacy(conf); + // port + conf.port = conf.port || 8080; - // key and cert - conf.https = conf.https || { - key: tryRead(dir, 'server.key'), - cert: tryRead(dir, 'server.crt') - }; + // hostname + conf.hostname; // '0.0.0.0' // shell, process name if (conf.shell && ~conf.shell.indexOf('/')) { - conf.shell = path.resolve(dir, conf.shell); + conf.shell = path.resolve(conf.dir, conf.shell); } - - // static directory - conf.static = conf.static || tryResolve(dir, 'static'); - - // Path to shell, or the process to execute in the terminal. conf.shell = conf.shell || process.env.SHELL || 'sh'; - // Arguments to shell, if they exist + // arguments to shell, if they exist conf.shellArgs = conf.shellArgs || []; - // $TERM - conf.term.termName = conf.termName || conf.term.termName; - if (!conf.term.termName) { - // tput -Txterm-256color longname - conf.term.termName = - exists('/usr/share/terminfo/x/xterm+256color') - || exists('/usr/share/terminfo/x/xterm-256color') - ? 'xterm-256color' - : 'xterm'; - } - conf.termName = conf.term.termName; + // static directory + conf.static = tryResolve(conf.dir, conf.static || 'static'); // limits conf.limitPerUser = conf.limitPerUser || Infinity; conf.limitGlobal = conf.limitGlobal || Infinity; + // sync session + conf.syncSession; // false + // session timeout if (typeof conf.sessionTimeout !== 'number') { conf.sessionTimeout = 10 * 60 * 1000; } - // users - if (conf.users && !Object.keys(conf.users).length) { - delete conf.users; - } + // log + conf.log; // true // cwd if (conf.cwd) { - conf.cwd = path.resolve(dir, conf.cwd); + conf.cwd = path.resolve(conf.dir, conf.cwd); } + // term + conf.term = conf.term || {}; + + conf.termName = conf.termName || conf.term.termName || terminfo(); + conf.term.termName = conf.termName; + + conf.term.termName; // 'xterm' + conf.term.geometry; // [80, 30] + conf.term.visualBell; // false + conf.term.popOnBell; // false + conf.term.cursorBlink; // true + conf.term.scrollback; // 1000 + conf.term.screenKeys; // false + conf.term.colors; // [] + conf.term.programFeatures; // false + conf.term.debug; // false + + // check legacy features + checkLegacy(conf); + return conf; } @@ -198,17 +164,7 @@ function checkLegacy(conf) { var out = []; if (conf.auth) { - if (conf.auth && conf.auth.username && !conf.auth.disabled) { - conf.users[conf.auth.username] = conf.auth.password; - } - logger('error', '`auth` is deprecated, please use `users` instead.'); - } - - if (conf.https && conf.https.key) { - conf.https = { - key: tryRead(conf.dir, conf.https.key), - cert: tryRead(conf.dir, conf.https.cert) - }; + logger.error('`auth` is deprecated, please use `users` instead.'); } if (conf.userScript) { @@ -229,10 +185,6 @@ function checkLegacy(conf) { + '`user.css` in `~/.tty.js/static/user.css` instead.'); } - if (conf.static) { - conf.static = tryResolve(conf.dir, conf.static); - } - if (conf.hooks) { out.push('' + '`hooks` is deprecated, please programmatically ' @@ -241,13 +193,30 @@ function checkLegacy(conf) { if (out.length) { out.forEach(function(out) { - logger('error', out); + logger.error(out); }); - logger('error', 'Exiting.'); + logger.error('Exiting.'); process.exit(1); } } +/** + * Terminfo + */ + +function terminfo() { + // tput -Txterm-256color longname + var terminfo = exists('/usr/share/terminfo/x/xterm+256color') + || exists('/usr/share/terminfo/x/xterm-256color'); + + // Default $TERM + var TERM = terminfo + ? 'xterm-256color' + : 'xterm'; + + return TERM; +} + /** * Daemonize */ @@ -269,6 +238,7 @@ function daemonize() { process.exit(code || 0); }); + // kill current stack process.once('uncaughtException', function() {}); throw 'stop'; } @@ -302,7 +272,7 @@ function help() { function parseArg() { var argv = process.argv.slice() - , opt = {} + , opt = { conf: {} } , arg; function getarg() { @@ -338,7 +308,7 @@ function parseArg() { switch (arg) { case '-p': case '--port': - opt.port = +argv.shift(); + opt.conf.port = +argv.shift(); break; case '-c': case '--config': @@ -458,7 +428,6 @@ function clone(obj) { exports.readConfig = readConfig; exports.checkConfig = checkConfig; -exports.schema = clone(schema); exports.xresources = readResources(); exports.helpers = { diff --git a/lib/tty.js b/lib/tty.js index e4590009..4a2e7fcd 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -203,7 +203,7 @@ Server.prototype._basicAuth = function() { var self = this; var conf = this.conf; - if (!conf.users) { + if (!Object.keys(conf.users).length) { return function(req, res, next) { next(); }; @@ -322,8 +322,8 @@ Session.prototype.uid = function() { if (this.server.conf.syncSession) { var req = this.req; return req.address.address - + ':' + req.address.port - + ':' + req.headers['user-agent']; + + '|' + req.address.port + + '|' + req.headers['user-agent']; } return Session.uid++ + ''; }; @@ -355,7 +355,7 @@ Session.prototype.warning = function() { Session.prototype._log = function(level, args) { if (typeof args[0] !== 'string') args.unshift(''); - var id = this.id.split(':')[0]; + var id = this.id.split('|')[0]; args[0] = '\x1b[1m' + id + '\x1b[m ' + args[0]; return this.server._log(level, args); }; From db8db4e5e4610af0aa69d565d9e0232c6e903355 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 27 Jun 2012 08:27:51 -0500 Subject: [PATCH 065/131] express compat --- lib/tty.js | 87 ++++++++++++++++++++++++++++++++++++------------------ 1 file changed, 59 insertions(+), 28 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index 4a2e7fcd..f16e64d5 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -9,7 +9,8 @@ var path = require('path') , fs = require('fs') - , Stream = require('stream').Stream; + , Stream = require('stream').Stream + , EventEmitter = require('events').EventEmitter; var express = require('express') , io = require('socket.io') @@ -18,6 +19,12 @@ var express = require('express') var config = require('./config') , logger = require('./logger'); +/** + * Compatibility + */ + +var newExpress = typeof express.createServer() === 'function'; + /** * Server */ @@ -29,42 +36,36 @@ function Server(conf) { conf = config.checkConfig(conf); - var self = this; - var app = conf.https && conf.https.key - ? express.createServer(conf.https) - : express.createServer(); - - // Inherit from express >= 3.0. - Object.getOwnPropertyNames(app).forEach(function(key) { - // A function here that has the original app - // closure-scoped would be a problem. - Object.defineProperty(self, key, Object.getOwnPropertyDescriptor(app, key)); - }); - - // Call init again for good measure, - // to potentially get rid of - // closure-scoped references - // to the original app. - this.init(); + if (newExpress) { + this.app = express.createServer(); + this.server = conf.https && conf.https.key + ? require('https').createServer(conf.https) + : require('http').createServer(); + this.server.on('request', this.app); + } else { + this.app = conf.https && conf.https.key + ? express.createServer(conf.https) + : express.createServer(); + this.server = this.app; + } this.sessions = {}; this.conf = conf; this._auth = this._basicAuth(); - this.io = io.listen(this, { + this.io = io.listen(this.server, { log: false }); - this._init(); + this.init(); + var self = this; this.on('listening', function() { - self.log('Listening on port %s.', self.conf.port); + self.log('Listening on port \x1b[1m%s\x1b[m.', self.conf.port); }); } -Server.prototype.__proto__ = express.createServer().__proto__; - -Server.prototype._init = function() { - this._init = function() {}; +Server.prototype.init = function() { + this.init = function() {}; this.initMiddleware(); this.initRoutes(); this.initIO(); @@ -100,7 +101,7 @@ Server.prototype.initMiddleware = function() { // static directory, this will be ignored. this.use(express.favicon(__dirname + '/../static/favicon.ico')); - this.use(this.router); + this.use(this.app.router); this.use(express.static(__dirname + '/../static')); }; @@ -278,11 +279,10 @@ Server.prototype._log = function(level, args) { return logger.apply(null, args); }; -Server.prototype._listen = express.createServer().listen; Server.prototype.listen = function(port, hostname, func) { port = port || this.conf.port || 8080; hostname = hostname || this.conf.hostname; - return this._listen(port, hostname, func); + return this.server.listen(port, hostname, func); }; /** @@ -501,6 +501,37 @@ Session.prototype.clearTimeout = function() { delete this.timeout; }; +/** + * "Inherit" Express Methods + */ + +// Methods +var proto = !newExpress + ? express.HTTPServer.prototype + : express.application; + +Object.keys(proto).forEach(function(key) { + if (Server.prototype[key]) return; + Server.prototype[key] = function() { + return this.app[key].apply(this.app, arguments); + }; +}); + +// Middleware +Object.getOwnPropertyNames(express).forEach(function(key) { + var prop = Object.getOwnPropertyDescriptor(express, key); + if (typeof prop.get !== 'function') return; + Object.defineProperty(Server, key, prop); +}); + +// Server Methods +Object.keys(EventEmitter.prototype).forEach(function(key) { + if (Server.prototype[key]) return; + Server.prototype[key] = function() { + return this.server[key].apply(this.server, arguments); + }; +}); + /** * Helpers */ From bd89f7a76356bbc94081cf1c892880db3648b051 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 27 Jun 2012 08:44:49 -0500 Subject: [PATCH 066/131] update package.json. stricter dependency versions. --- package.json | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index 51659234..5aea5d5c 100644 --- a/package.json +++ b/package.json @@ -10,11 +10,11 @@ "repository": "git://github.com/chjj/tty.js.git", "homepage": "https://github.com/chjj/tty.js", "bugs": { "url": "https://github.com/chjj/tty.js/issues" }, - "keywords": [ "tty", "terminal" ], - "tags": [ "tty", "terminal" ], + "keywords": ["tty", "terminal"], + "tags": ["tty", "terminal"], "dependencies": { - "express": ">= 2.5.8", - "socket.io": ">= 0.8.7", - "pty.js": ">= 0.1.2" + "express": "3.0.0beta4", + "socket.io": "0.9.6", + "pty.js": "0.1.2" } } From bbe39f32e5b1700340ded7ea702a87b607502b35 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 27 Jun 2012 08:45:58 -0500 Subject: [PATCH 067/131] v0.2.7 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 5aea5d5c..5d006bd6 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.6", + "version": "0.2.7", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From eb9b92ae8fb88d97684951958508c05609a20107 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 11 Jul 2012 12:38:10 -0500 Subject: [PATCH 068/131] drop older express compatibility. --- lib/tty.js | 29 ++++++----------------------- 1 file changed, 6 insertions(+), 23 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index f16e64d5..7d746029 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -19,12 +19,6 @@ var express = require('express') var config = require('./config') , logger = require('./logger'); -/** - * Compatibility - */ - -var newExpress = typeof express.createServer() === 'function'; - /** * Server */ @@ -36,18 +30,11 @@ function Server(conf) { conf = config.checkConfig(conf); - if (newExpress) { - this.app = express.createServer(); - this.server = conf.https && conf.https.key - ? require('https').createServer(conf.https) - : require('http').createServer(); - this.server.on('request', this.app); - } else { - this.app = conf.https && conf.https.key - ? express.createServer(conf.https) - : express.createServer(); - this.server = this.app; - } + this.app = express.createServer(); + this.server = conf.https && conf.https.key + ? require('https').createServer(conf.https) + : require('http').createServer(); + this.server.on('request', this.app); this.sessions = {}; this.conf = conf; @@ -506,11 +493,7 @@ Session.prototype.clearTimeout = function() { */ // Methods -var proto = !newExpress - ? express.HTTPServer.prototype - : express.application; - -Object.keys(proto).forEach(function(key) { +Object.keys(express.application).forEach(function(key) { if (Server.prototype[key]) return; Server.prototype[key] = function() { return this.app[key].apply(this.app, arguments); From 19a886be7fdd584879648e74a91d830ed51e7929 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 22 Jul 2012 03:33:30 -0500 Subject: [PATCH 069/131] sync session --- lib/tty.js | 45 +++++++++++++++++++++++++++++++-------------- static/tty.js | 31 ++++++++++++++++++++++--------- 2 files changed, 53 insertions(+), 23 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index 7d746029..97b88a11 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -162,6 +162,9 @@ Server.prototype.handleAuth = function(data, next) { Server.prototype.handleConnection = function(socket) { var session = new Session(this, socket); + // XXX Possibly wrap socket events from inside Session + // constructor, and do: session.on('create') + // or session.on('create term'). socket.on('create', function(cols, rows, func) { return session.handleCreate(cols, rows, func); }); @@ -292,10 +295,13 @@ function Session(server, socket) { // Kill/sync older session. if (conf.syncSession) { - if (sessions[this.id]) { - this.sync(sessions[this.id].terms); - sessions[this.id].destroy(); - delete sessions[this.id]; + var stale = sessions[this.id]; + if (stale) { + stale.disconnect(); + stale.socket = socket; + stale.sync(); + stale.log('Session \x1b[1m%s\x1b[m resumed.', stale.id); + return stale; } } @@ -315,16 +321,14 @@ Session.prototype.uid = function() { return Session.uid++ + ''; }; -Session.prototype.destroy = function() { +Session.prototype.disconnect = function() { try { + this.socket._events = {}; + this.socket.$emit = function() {}; this.socket.disconnect(); } catch (e) { ; } - // This will keep the terms alive after - // the syncSession `if` clause in the constructor. - // Need to clear the timeout set by handleDisconnect. - // It's also necessary for clearing the timeout period. this.clearTimeout(); }; @@ -347,9 +351,22 @@ Session.prototype._log = function(level, args) { return this.server._log(level, args); }; -Session.prototype.sync = function(terms) { - if (terms) this.terms = terms; - this.socket.emit('sync', this.terms); +Session.prototype.sync = function() { + var self = this + , terms = {}; + + Object.keys(this.terms).forEach(function(key) { + var term = self.terms[key]; + terms[key] = { + id: term.pty, + pty: term.pty, + cols: term.cols, + rows: term.rows, + process: sanitize(term.process) + }; + }); + + this.socket.emit('sync', terms); }; Session.prototype.handleCreate = function(cols, rows, func) { @@ -378,13 +395,13 @@ Session.prototype.handleCreate = function(cols, rows, func) { terms[id] = term; term.on('data', function(data) { - socket.emit('data', id, data); + self.socket.emit('data', id, data); }); term.on('close', function() { // Make sure it closes // on the clientside. - socket.emit('kill', id); + self.socket.emit('kill', id); // Ensure removal. if (terms[id]) delete terms[id]; diff --git a/static/tty.js b/static/tty.js index 82418de2..81d70bde 100644 --- a/static/tty.js +++ b/static/tty.js @@ -86,7 +86,7 @@ tty.open = function() { tty.socket.on('connect', function() { tty.reset(); - new Window; + //new Window; tty.emit('connect'); }); @@ -100,19 +100,32 @@ tty.open = function() { tty.terms[id]._destroy(); }); + // XXX Clean this up. tty.socket.on('sync', function(terms) { console.log('Attempting to sync...'); console.log(terms); + tty.reset(); - terms.forEach(function(term) { - var emit = tty.socket.emit; - tty.socket.emit = function() {}; - var win = new Window; - Object.keys(term).forEach(function(key) { - win.tabs[0][key] = term[key]; - }); - tty.socket.emit = emit; + + var emit = tty.socket.emit; + tty.socket.emit = function() {}; + + Object.keys(terms).forEach(function(key) { + var data = terms[key] + , win = new Window + , tab = win.tabs[0]; + + delete tty.terms[tab.id]; + tab.pty = data.pty; + tab.id = data.id; + tty.terms[data.id] = tab; + win.resize(data.cols, data.rows); + tab.setProcessName(data.process); + tty.emit('open tab', tab); + tab.emit('open'); }); + + tty.socket.emit = emit; }); // We would need to poll the os on the serverside From 31a2215ce28b324ef915b6e06af576b8af1a8fa5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 22 Jul 2012 04:25:06 -0500 Subject: [PATCH 070/131] minor refactor and comments. --- lib/tty.js | 45 +++++++++++++++++++++++++-------------------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index 97b88a11..5409ad3e 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -59,8 +59,8 @@ Server.prototype.init = function() { }; Server.prototype.initMiddleware = function() { - var self = this; - var conf = this.conf; + var self = this + , conf = this.conf; this.use(function(req, res, next) { var setHeader = res.setHeader; @@ -105,8 +105,8 @@ Server.prototype.initRoutes = function() { }; Server.prototype.handleOptions = function(req, res, next) { - var self = this; - var conf = this.conf; + var self = this + , conf = this.conf; res.contentType('.js'); fs.readFile(conf.json, 'utf8', function(err, data) { @@ -132,8 +132,8 @@ Server.prototype.handleOptions = function(req, res, next) { }; Server.prototype.initIO = function() { - var self = this; - var io = this.io; + var self = this + , io = this.io; io.configure(function() { io.disable('log'); @@ -191,8 +191,8 @@ Server.prototype.handleConnection = function(socket) { }; Server.prototype._basicAuth = function() { - var self = this; - var conf = this.conf; + var self = this + , conf = this.conf; if (!Object.keys(conf.users).length) { return function(req, res, next) { @@ -285,10 +285,10 @@ function Session(server, socket) { this.terms = {}; this.req = socket.handshake; - var conf = this.server.conf; - var terms = this.terms; - var sessions = this.server.sessions; - var req = socket.handshake; + var conf = this.server.conf + , terms = this.terms + , sessions = this.server.sessions + , req = socket.handshake; this.user = req.user; this.id = req.user || this.uid(); @@ -325,7 +325,12 @@ Session.prototype.disconnect = function() { try { this.socket._events = {}; this.socket.$emit = function() {}; + // another way to ensure handleDisconnect + // doesnt get called by disconnect? + //var handleDisconnect = this.handleDisconnect; + //this.handleDisconnect = function() {}; this.socket.disconnect(); + //this.handleDisconnect = handleDisconnect; } catch (e) { ; } @@ -370,10 +375,10 @@ Session.prototype.sync = function() { }; Session.prototype.handleCreate = function(cols, rows, func) { - var self = this; - var terms = this.terms; - var conf = this.server.conf; - var socket = this.socket; + var self = this + , terms = this.terms + , conf = this.server.conf + , socket = this.socket; var len = Object.keys(terms).length , term @@ -455,10 +460,10 @@ Session.prototype.handleProcess = function(id, func) { }; Session.prototype.handleDisconnect = function() { - var self = this; - var terms = this.terms; - var sessions = this.server.sessions; - var conf = this.server.conf; + var self = this + , terms = this.terms + , sessions = this.server.sessions + , conf = this.server.conf; this.log('Client disconnected.'); From e45ad55b9dad50e817788cf4cd1220fcdaa8fcc4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 22 Jul 2012 04:32:59 -0500 Subject: [PATCH 071/131] remove comments explaining a way to avoid handleDisconnect call. --- lib/tty.js | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index 5409ad3e..65c7a42a 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -325,12 +325,7 @@ Session.prototype.disconnect = function() { try { this.socket._events = {}; this.socket.$emit = function() {}; - // another way to ensure handleDisconnect - // doesnt get called by disconnect? - //var handleDisconnect = this.handleDisconnect; - //this.handleDisconnect = function() {}; this.socket.disconnect(); - //this.handleDisconnect = handleDisconnect; } catch (e) { ; } From 9c632c35e56b7a23e820524ac437353cfe661792 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 22 Jul 2012 04:54:16 -0500 Subject: [PATCH 072/131] send SIGWINCH to processes during sync. --- lib/tty.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/lib/tty.js b/lib/tty.js index 65c7a42a..f278794c 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -366,6 +366,16 @@ Session.prototype.sync = function() { }; }); + // XXX Ideally shouldn't need a setTimeout. + // Should use a callback from sync instead. + setTimeout(function() { + Object.keys(self.terms).forEach(function(key) { + // Send SIGWINCH to our processes, and hopefully + // they will redraw for our resumed session. + self.terms[key].kill('SIGWINCH'); + }); + }, 1000); + this.socket.emit('sync', terms); }; From 14b5956e8b6c7d8140ba1d92ab18ecbc93449498 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 22 Jul 2012 05:22:15 -0500 Subject: [PATCH 073/131] refactor, add -k option, set default rows to 24. --- README.md | 4 ++-- bin/tty.js | 7 +++---- lib/config.js | 44 +++++++++++++++++++++++++++++++++++--------- lib/tty.js | 3 +++ static/term.js | 2 +- static/tty.js | 1 - 6 files changed, 44 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 4f8a90c8..1468ade9 100644 --- a/README.md +++ b/README.md @@ -75,12 +75,12 @@ JSON file. An example configuration file looks like: "limitGlobal": 10000, "limitPerUser": 1000, "cwd": ".", - "syncSession": true, + "syncSession": false, "sessionTimeout": 600000, "log": true, "term": { "termName": "xterm", - "geometry": [80, 30], + "geometry": [80, 24], "scrollback": 1000, "visualBell": false, "popOnBell": false, diff --git a/bin/tty.js b/bin/tty.js index ba11552e..e1c35ad5 100755 --- a/bin/tty.js +++ b/bin/tty.js @@ -3,9 +3,8 @@ process.title = 'tty.js'; var tty = require('../'); -var conf = tty.config.readConfig(); -var app = tty.createServer(conf); -app.listen(); +var conf = tty.config.readConfig() + , app = tty.createServer(conf); -module.exports = app; +app.listen(); diff --git a/lib/config.js b/lib/config.js index 9c776989..bee45ee8 100644 --- a/lib/config.js +++ b/lib/config.js @@ -140,7 +140,7 @@ function checkConfig(conf) { conf.term.termName = conf.termName; conf.term.termName; // 'xterm' - conf.term.geometry; // [80, 30] + conf.term.geometry; // [80, 24] conf.term.visualBell; // false conf.term.popOnBell; // false conf.term.cursorBlink; // true @@ -224,8 +224,8 @@ function terminfo() { function daemonize() { if (process.env.IS_DAEMONIC) return; - var argv = process.argv.slice() - , spawn = require('child_process').spawn + var spawn = require('child_process').spawn + , argv = process.argv.slice() , code; argv = argv.map(function(arg) { @@ -238,9 +238,7 @@ function daemonize() { process.exit(code || 0); }); - // kill current stack - process.once('uncaughtException', function() {}); - throw 'stop'; + stop(); } /** @@ -261,9 +259,28 @@ function help() { [__dirname + '/../man/tty.js.1'], options); - // kill current stack - process.once('uncaughtException', function() {}); - throw 'stop'; + stop(); +} + +/** + * Kill + */ + +function killall() { + var spawn = require('child_process').spawn; + + var options = { + cwd: process.cwd(), + env: process.env, + setsid: false, + customFds: [0, 1, 2] + }; + + spawn('/bin/sh', + ['-c', 'kill $(ps ax | grep -v grep | grep tty.js | awk \'{print $1}\')'], + options); + + stop(); } /** @@ -326,6 +343,10 @@ function parseArg() { case '--daemonize': daemonize(); break; + case '-k': + case '--kill': + killall(); + break; default: break; } @@ -422,6 +443,11 @@ function clone(obj) { return merge({}, obj); } +function stop() { + process.once('uncaughtException', function() {}); + throw 'stop'; +} + /** * Expose */ diff --git a/lib/tty.js b/lib/tty.js index f278794c..ed89302b 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -297,6 +297,9 @@ function Session(server, socket) { if (conf.syncSession) { var stale = sessions[this.id]; if (stale) { + // Possibly do something like this instead: + // if (!stale.socket.disconnected) + // return this.id += '~', sessions[this.id] = this; stale.disconnect(); stale.socket = socket; stale.sync(); diff --git a/static/term.js b/static/term.js index 94893343..b4f7ee74 100644 --- a/static/term.js +++ b/static/term.js @@ -276,7 +276,7 @@ Terminal.colors[257] = Terminal.defaultColors.fg; */ Terminal.termName = 'xterm'; -Terminal.geometry = [80, 30]; +Terminal.geometry = [80, 24]; Terminal.cursorBlink = true; Terminal.visualBell = false; Terminal.popOnBell = false; diff --git a/static/tty.js b/static/tty.js index 81d70bde..770da7d4 100644 --- a/static/tty.js +++ b/static/tty.js @@ -86,7 +86,6 @@ tty.open = function() { tty.socket.on('connect', function() { tty.reset(); - //new Window; tty.emit('connect'); }); From be2d3ef59716453317f654a6218abfec62367beb Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 23 Jul 2012 14:16:21 -0500 Subject: [PATCH 074/131] misc --- lib/logger.js | 4 ++-- lib/tty.js | 35 +++++++++++++++++++---------------- static/tty.js | 23 ++++++++++++----------- 3 files changed, 33 insertions(+), 29 deletions(-) diff --git a/lib/logger.js b/lib/logger.js index 3ea2aa46..5d26e9f4 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -11,7 +11,7 @@ var slice = Array.prototype.slice */ function logger(level) { - var args = Array.prototype.slice.call(arguments, 1); + var args = slice.call(arguments, 1); if (typeof args[0] !== 'string') args.unshift(''); @@ -37,7 +37,7 @@ logger.isatty = [isatty(0), isatty(1), isatty(2)]; logger.levels = { 'log': [34, 'log'], 'error': [41, 'error'], - 'warning': [31, 'error'] // 31, 33, 91 + 'warning': [31, 'error'] }; logger.prefix = 'tty.js'; diff --git a/lib/tty.js b/lib/tty.js index ed89302b..e0b64ddf 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -369,15 +369,12 @@ Session.prototype.sync = function() { }; }); - // XXX Ideally shouldn't need a setTimeout. - // Should use a callback from sync instead. - setTimeout(function() { - Object.keys(self.terms).forEach(function(key) { - // Send SIGWINCH to our processes, and hopefully - // they will redraw for our resumed session. - self.terms[key].kill('SIGWINCH'); - }); - }, 1000); + // Possibly call inside setTimeout/callback. + Object.keys(self.terms).forEach(function(key) { + // Send SIGWINCH to our processes, and hopefully + // they will redraw for our resumed session. + self.terms[key].kill('SIGWINCH'); + }); this.socket.emit('sync', terms); }; @@ -558,19 +555,25 @@ function sanitize(file) { } function applyConfig() { - for (var key in Terminal.options) { - if (!Object.prototype.hasOwnProperty.call(Terminal.options, key)) continue; - if (key === 'colors') { - var l = Terminal.options.colors.length - , i = 0; + var hasOwnProperty = Object.prototype.hasOwnProperty; - for (; i < l; i++) { - Terminal.colors[i] = Terminal.options.colors[i]; + for (var key in Terminal.options) { + if (!hasOwnProperty.call(Terminal.options, key)) continue; + if (typeof Terminal.options[key] === 'object' && Terminal.options[key]) { + if (!Terminal[key]) { + Terminal[key] = Terminal.options[key]; + continue; + } + for (var k in Terminal.options[key]) { + if (hasOwnProperty.call(Terminal.options[key], k)) { + Terminal[key][k] = Terminal.options[key][k]; + } } } else { Terminal[key] = Terminal.options[key]; } } + delete Terminal.options; } diff --git a/static/tty.js b/static/tty.js index 770da7d4..06f6a205 100644 --- a/static/tty.js +++ b/static/tty.js @@ -799,6 +799,17 @@ Tab.prototype._ignoreNext = function() { * Program-specific Features */ +Tab.scrollable = { + irssi: true, + man: true, + less: true, + htop: true, + top: true, + w3m: true, + lynx: true, + mocp: true +}; + Tab.prototype._bindMouse = Tab.prototype.bindMouse; Tab.prototype.bindMouse = function() { @@ -810,19 +821,9 @@ Tab.prototype.bindMouse = function() { ? 'mousewheel' : 'DOMMouseScroll'; - var programs = { - irssi: true, - man: true, - less: true, - htop: true, - top: true, - w3m: true, - lynx: true - }; - on(self.element, wheelEvent, function(ev) { if (self.mouseEvents) return; - if (!programs[self.process]) return; + if (!Tab.scrollable[self.process]) return; if ((ev.type === 'mousewheel' && ev.wheelDeltaY > 0) || (ev.type === 'DOMMouseScroll' && ev.detail < 0)) { From 9e6a207243f031ed2982b537145b23f34f6af19c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 23 Jul 2012 19:59:41 -0500 Subject: [PATCH 075/131] redraw terminals better on sync --- lib/tty.js | 24 +++++++++++++++++++++--- 1 file changed, 21 insertions(+), 3 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index e0b64ddf..e88d84a3 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -356,7 +356,8 @@ Session.prototype._log = function(level, args) { Session.prototype.sync = function() { var self = this - , terms = {}; + , terms = {} + , queue = []; Object.keys(this.terms).forEach(function(key) { var term = self.terms[key]; @@ -369,13 +370,30 @@ Session.prototype.sync = function() { }; }); - // Possibly call inside setTimeout/callback. Object.keys(self.terms).forEach(function(key) { + var term = self.terms[key] + , cols = term.cols + , rows = term.rows; + + // A tricky way to get processes to redraw. + // Some programs won't redraw unless the + // terminal has actually been resized. + term.resize(cols + 1, rows + 1); + queue.push(function() { + term.resize(cols, rows); + }); + // Send SIGWINCH to our processes, and hopefully // they will redraw for our resumed session. - self.terms[key].kill('SIGWINCH'); + // self.terms[key].kill('SIGWINCH'); }); + setTimeout(function() { + queue.forEach(function(item) { + item(); + }); + }, 30); + this.socket.emit('sync', terms); }; From 95d2db9557620e5afc7ea39bbb97bd11237bb860 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 23 Jul 2012 20:00:29 -0500 Subject: [PATCH 076/131] add localOnly option --- README.md | 5 ++++- lib/config.js | 7 ++++++- lib/tty.js | 24 ++++++++++++++++++++---- 3 files changed, 30 insertions(+), 6 deletions(-) diff --git a/README.md b/README.md index 1468ade9..a5a0f206 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Bellard's vt100 for [jslinux](http://bellard.org/jslinux/). - Ability to efficiently render programs: vim, mc, irssi, vifm, etc. - Support for xterm mouse events - 256 color support +- Persistent sessions ## Install @@ -33,7 +34,7 @@ $ npm install tty.js ## Usage -tty.js is an app, but it's also possible to hook into it programatically +tty.js is an app, but it's also possible to hook into it programatically. ``` js var tty = require('tty.js'); @@ -74,10 +75,12 @@ JSON file. An example configuration file looks like: "static": "./static", "limitGlobal": 10000, "limitPerUser": 1000, + "localOnly": false, "cwd": ".", "syncSession": false, "sessionTimeout": 600000, "log": true, + "debug": false, "term": { "termName": "xterm", "geometry": [80, 24], diff --git a/lib/config.js b/lib/config.js index bee45ee8..9113c701 100644 --- a/lib/config.js +++ b/lib/config.js @@ -117,6 +117,9 @@ function checkConfig(conf) { conf.limitPerUser = conf.limitPerUser || Infinity; conf.limitGlobal = conf.limitGlobal || Infinity; + // local + conf.localOnly = !!conf.localOnly; + // sync session conf.syncSession; // false @@ -148,7 +151,9 @@ function checkConfig(conf) { conf.term.screenKeys; // false conf.term.colors; // [] conf.term.programFeatures; // false - conf.term.debug; // false + + conf.debug = conf.debug || conf.term.debug || false; + conf.term.debug = conf.debug; // false // check legacy features checkLegacy(conf); diff --git a/lib/tty.js b/lib/tty.js index e88d84a3..8e437b18 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -28,7 +28,8 @@ function Server(conf) { return new Server(conf); } - conf = config.checkConfig(conf); + var self = this + , conf = config.checkConfig(conf); this.app = express.createServer(); this.server = conf.https && conf.https.key @@ -43,21 +44,36 @@ function Server(conf) { log: false }); - this.init(); - - var self = this; this.on('listening', function() { self.log('Listening on port \x1b[1m%s\x1b[m.', self.conf.port); }); + + this.init(); } Server.prototype.init = function() { this.init = function() {}; + if (this.conf.localOnly) this.initLocal(); this.initMiddleware(); this.initRoutes(); this.initIO(); }; +Server.prototype.initLocal = function() { + this.warning('Only accepting local connections.'), + this.server.on('connection', function(socket) { + var address = socket.remoteAddress; + if (address !== '127.0.0.1' && address !== '::1') { + try { + socket.destroy(); + } catch (e) { + ; + } + self.log('Attempted connection from %s. Refused.', address); + } + }); +}; + Server.prototype.initMiddleware = function() { var self = this , conf = this.conf; From 7ed347748b10f373a6f501b41e278f009178fdde Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 14 Sep 2012 17:04:02 -0500 Subject: [PATCH 077/131] fix checkConfig --- lib/config.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/config.js b/lib/config.js index 9113c701..4c6d1618 100644 --- a/lib/config.js +++ b/lib/config.js @@ -59,12 +59,12 @@ function readConfig(file) { } function checkConfig(conf) { - conf = clone(conf || {}); - if (typeof conf === 'string') { return readConfig(conf); } + conf = clone(conf || {}); + if (conf.config) { var file = conf.config; delete conf.config; From e3c4ddac65d2d545695fe06089ed35274efb0e31 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 14 Sep 2012 17:13:11 -0500 Subject: [PATCH 078/131] stay up to date with express and socket.io --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 5d006bd6..63c24c93 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "keywords": ["tty", "terminal"], "tags": ["tty", "terminal"], "dependencies": { - "express": "3.0.0beta4", - "socket.io": "0.9.6", + "express": "3.0.0rc4", + "socket.io": "0.9.10", "pty.js": "0.1.2" } } From c1c2c5554e897969cd3eda5def20fe57badea5a4 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 14 Sep 2012 17:16:46 -0500 Subject: [PATCH 079/131] fix initLocal --- lib/tty.js | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/tty.js b/lib/tty.js index 8e437b18..38c132f3 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -60,6 +60,7 @@ Server.prototype.init = function() { }; Server.prototype.initLocal = function() { + var self = this; this.warning('Only accepting local connections.'), this.server.on('connection', function(socket) { var address = socket.remoteAddress; From 067dc429e71b4120d1c029c87363d1d4343cd9d8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 14 Sep 2012 17:31:13 -0500 Subject: [PATCH 080/131] get rid of express createServer warning. --- lib/tty.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/tty.js b/lib/tty.js index 38c132f3..ceb5af72 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -31,7 +31,7 @@ function Server(conf) { var self = this , conf = config.checkConfig(conf); - this.app = express.createServer(); + this.app = express(); this.server = conf.https && conf.https.key ? require('https').createServer(conf.https) : require('http').createServer(); From d69507b03b681e0a6d21f440b7e14f3bd4c3bf52 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 22 Oct 2012 02:50:36 -0500 Subject: [PATCH 081/131] v0.2.8 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 63c24c93..2eabdefe 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.7", + "version": "0.2.8", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From 038fd11709bc44a26ff94487da42da9ca0c1972c Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 22 Oct 2012 14:54:09 -0500 Subject: [PATCH 082/131] fix terminal reset with event emitter. --- static/term.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/static/term.js b/static/term.js index b4f7ee74..f597e29b 100644 --- a/static/term.js +++ b/static/term.js @@ -42,7 +42,7 @@ var window = this */ function EventEmitter() { - this._events = {}; + this._events = this._events || {}; } EventEmitter.prototype.addListener = function(type, listener) { From ad73315f529f82678362c675c4d982faf4af547f Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 22 Oct 2012 14:54:21 -0500 Subject: [PATCH 083/131] add simple terminal example. --- example/index.html | 53 ++++++++++++++++++++++++++ example/index.js | 93 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 146 insertions(+) create mode 100644 example/index.html create mode 100644 example/index.js diff --git a/example/index.html b/example/index.html new file mode 100644 index 00000000..9ec992fb --- /dev/null +++ b/example/index.html @@ -0,0 +1,53 @@ + +tty.js + + +

tty.js

+ + + diff --git a/example/index.js b/example/index.js new file mode 100644 index 00000000..e80aa3cf --- /dev/null +++ b/example/index.js @@ -0,0 +1,93 @@ +/** + * tty.js + * Copyright (c) 2012, Christopher Jeffrey (MIT License) + */ + +var http = require('http') + , express = require('express') + , io = require('socket.io') + , pty = require('pty.js'); + +/** + * tty.js + */ + +process.title = 'tty.js'; + +/** + * Open Terminal + */ + +var buff = [] + , socket + , term; + +var term = pty.fork(process.env.SHELL || 'sh', [], { + name: 'xterm', + cols: 80, + rows: 24, + cwd: process.env.HOME +}); + +term.on('data', function(data) { + return !socket + ? buff.push(data) + : socket.emit('data', data); +}); + +console.log('' + + 'Created shell with pty master/slave' + + ' pair (master: %d, pid: %d)', + term.fd, term.pid); + +/** + * App & Server + */ + +var app = express() + , server = http.createServer(app); + +app.use(function(req, res, next) { + var setHeader = res.setHeader; + res.setHeader = function(name) { + switch (name) { + case 'Cache-Control': + case 'Last-Modified': + case 'ETag': + return; + } + return setHeader.apply(res, arguments); + }; + next(); +}); + +app.use(express.static(__dirname)); +app.use(express.static(__dirname + '/../static')); + +server.listen(8080); + +/** + * Sockets + */ + +io = io.listen(server); + +io.configure(function() { + io.disable('log'); +}); + +io.sockets.on('connection', function(sock) { + socket = sock; + + socket.on('data', function(data) { + term.write(data); + }); + + socket.on('disconnect', function() { + socket = null; + }); + + while (buff.length) { + socket.emit('data', buff.shift()); + } +}); From ec30c47e81e7e478b6d71689ed0229d6023b3076 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 24 Oct 2012 11:16:23 -0500 Subject: [PATCH 084/131] slightly improve example. --- example/index.html | 16 ++++++++-------- 1 file changed, 8 insertions(+), 8 deletions(-) diff --git a/example/index.html b/example/index.html index 9ec992fb..2bd876e3 100644 --- a/example/index.html +++ b/example/index.html @@ -33,17 +33,17 @@

tty.js

var term = new Terminal(80, 24) , socket = io.connect(); - term.on('data', function(data) { - socket.emit('data', data); - }); + socket.on('connect', function() { + term.on('data', function(data) { + socket.emit('data', data); + }); - term.on('title', function(title) { - document.title = title; - }); + term.on('title', function(title) { + document.title = title; + }); - term.open(); + term.open(); - socket.on('connect', function() { socket.on('data', function(data) { term.write(data); }); From e3f3b95d6b968bdb8da73acbc16069d4b16d5e17 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Mon, 19 Nov 2012 03:42:10 -0600 Subject: [PATCH 085/131] include mit license in term.js. see #47. --- static/term.js | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/static/term.js b/static/term.js index f597e29b..0bb526bd 100644 --- a/static/term.js +++ b/static/term.js @@ -1,19 +1,34 @@ /** * tty.js - an xterm emulator - * Christopher Jeffrey (https://github.com/chjj/tty.js) * - * Originally forked from (with the author's permission): + * Copyright (c) 2012, Christopher Jeffrey (https://github.com/chjj/tty.js) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. * - * Fabrice Bellard's javascript vt100 for jslinux: - * http://bellard.org/jslinux/ - * Copyright (c) 2011 Fabrice Bellard - * (Redistribution or commercial use is prohibited - * without the author's permission.) + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. * - * The original design remains. The terminal itself - * has been extended to include xterm CSI codes, among - * other features. -*/ + * Originally forked from (with the author's permission): + * Fabrice Bellard's javascript vt100 for jslinux: + * http://bellard.org/jslinux/ + * Copyright (c) 2011 Fabrice Bellard + * The original design remains. The terminal itself + * has been extended to include xterm CSI codes, among + * other features. + */ ;(function() { From a2f67ee479155c17794da548cfa5c19a6e50bd0b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 14 Dec 2012 00:10:18 -0600 Subject: [PATCH 086/131] add add https.disabled option. --- lib/config.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/config.js b/lib/config.js index 4c6d1618..927dabcf 100644 --- a/lib/config.js +++ b/lib/config.js @@ -90,7 +90,7 @@ function checkConfig(conf) { // https conf.https = conf.https || conf.ssl || conf.tls || {}; - conf.https = { + conf.https = !conf.https.disabled && { key: tryRead(conf.dir, conf.https.key || 'server.key') || conf.https.key, cert: tryRead(conf.dir, conf.https.cert || 'server.crt') || conf.https.cert }; From 55e1a9b421a335ca5eaf02df9a4cc42215da0d15 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 3 Jan 2013 08:54:28 -0600 Subject: [PATCH 087/131] misc. comments. --- LICENSE | 2 +- README.md | 2 +- example/index.html | 2 +- example/index.js | 2 +- lib/config.js | 2 +- lib/logger.js | 2 +- lib/tty.js | 2 +- man/tty.js.1 | 8 +++++++- static/style.css | 2 +- static/term.js | 2 +- static/tty.js | 2 +- 11 files changed, 17 insertions(+), 11 deletions(-) diff --git a/LICENSE b/LICENSE index 40597477..259500c9 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2011-2012, Christopher Jeffrey (https://github.com/chjj/) +Copyright (c) 2012-2013, Christopher Jeffrey (https://github.com/chjj/) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index a5a0f206..342177ea 100644 --- a/README.md +++ b/README.md @@ -158,6 +158,6 @@ The distance to go before full xterm compatibility. ## License -Copyright (c) 2012, Christopher Jeffrey (MIT License) +Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) [1]: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking diff --git a/example/index.html b/example/index.html index 2bd876e3..f08d0bcc 100644 --- a/example/index.html +++ b/example/index.html @@ -2,7 +2,7 @@ tty.js

tty.js

- + - - diff --git a/test/index.js b/test/index.js deleted file mode 100644 index 4118cc28..00000000 --- a/test/index.js +++ /dev/null @@ -1,25 +0,0 @@ -var path = require('path') - , fs = require('fs'); - -var express = require('express') - , app = express.createServer(); - -app.use(function(req, res, next) { - var setHeader = res.setHeader; - res.setHeader = function(name) { - switch (name) { - case 'Cache-Control': - case 'Last-Modified': - case 'ETag': - return; - } - return setHeader.apply(res, arguments); - }; - next(); -}); - -app.use(express.static(__dirname)); - -app.use(express.static(__dirname + '/../static')); - -app.listen(8080); From 2c59710032b5875b1ee82949d52cf4ab3b1a3b7a Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 09:39:56 -0500 Subject: [PATCH 107/131] refactor new tty.open code. --- package.json | 4 ++-- static/tty.js | 14 ++++++++------ 2 files changed, 10 insertions(+), 8 deletions(-) diff --git a/package.json b/package.json index 41dac2d5..ba8b5223 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "repository": "git://github.com/chjj/tty.js.git", "homepage": "https://github.com/chjj/tty.js", "bugs": { "url": "https://github.com/chjj/tty.js/issues" }, - "keywords": ["tty", "terminal"], - "tags": ["tty", "terminal"], + "keywords": ["tty", "terminal", "term"], + "tags": ["tty", "terminal", "term"], "dependencies": { "express": "3.1.0", "socket.io": "0.9.13", diff --git a/static/tty.js b/static/tty.js index 85aa4ddd..1ee7640d 100644 --- a/static/tty.js +++ b/static/tty.js @@ -54,13 +54,15 @@ tty.elements; */ tty.open = function() { - var - pathComponents = document.location.pathname.split('/'), - // Strip last part (either index.html or "", presumably) - baseURL = pathComponents.slice(0,pathComponents.length-1).join('/') + '/', - resource = baseURL.substring(1) + "socket.io"; + if (document.location.pathname) { + var parts = document.location.pathname.split('/') + , base = parts.slice(0, parts.length - 1).join('/') + '/' + , resource = base.substring(1) + 'socket.io'; - tty.socket = io.connect(null, { resource: resource }); + tty.socket = io.connect(null, { resource: resource }); + } else { + tty.socket = io.connect(); + } tty.windows = []; tty.terms = {}; From 64601388a40b97acc56deb9b80b10d973d59bfbf Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 09:41:45 -0500 Subject: [PATCH 108/131] v0.2.11 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index ba8b5223..bac9d0a9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.10", + "version": "0.2.11", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From f412c3aa27ffeb6ef6c3a153d5ca8d73c1abd1ae Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 09:49:11 -0500 Subject: [PATCH 109/131] move example to term.js repo. --- example/index.html | 53 ---------------------------------------------- 1 file changed, 53 deletions(-) delete mode 100644 example/index.html diff --git a/example/index.html b/example/index.html deleted file mode 100644 index 333b1593..00000000 --- a/example/index.html +++ /dev/null @@ -1,53 +0,0 @@ - -tty.js - - -

tty.js

- - - From c873d4d3bf5d4a8d10254958c335339914654afe Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 09:50:14 -0500 Subject: [PATCH 110/131] v0.2.12-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index bac9d0a9..9cf99f6b 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.11", + "version": "0.2.12-1", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From 829834d1f0b9ba465066ef1dc2385527e1782b20 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 10:44:07 -0500 Subject: [PATCH 111/131] use term.js v0.0.2. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 9cf99f6b..248fbd9b 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "express": "3.1.0", "socket.io": "0.9.13", "pty.js": "0.2.3", - "term.js": "0.0.1" + "term.js": "0.0.2" }, "engines": { "node": ">= 0.8.0" } } From f613debfa1bbe9b41174155e3e33e9a8fb5791a6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 10:44:22 -0500 Subject: [PATCH 112/131] v0.2.12-2 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 248fbd9b..a5f575e4 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.12-1", + "version": "0.2.12-2", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From df55fc4cbf24b4f2e71ced00ec2a914208be262b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 8 Aug 2013 11:00:26 -0500 Subject: [PATCH 113/131] readme. package.json tags. --- README.md | 6 ++++++ package.json | 4 ++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e8b2a12c..74a751c0 100644 --- a/README.md +++ b/README.md @@ -3,6 +3,12 @@ A terminal in your browser using node.js and socket.io. Based on Fabrice Bellard's vt100 for [jslinux](http://bellard.org/jslinux/). +For the standalone web terminal, see +[**term.js**](https://github.com/chjj/term.js). + +For the lowlevel terminal spawner, see +[**pty.js**](https://github.com/chjj/pty.js). + ## Screenshots ### irssi diff --git a/package.json b/package.json index a5f575e4..a5083ce8 100644 --- a/package.json +++ b/package.json @@ -10,8 +10,8 @@ "repository": "git://github.com/chjj/tty.js.git", "homepage": "https://github.com/chjj/tty.js", "bugs": { "url": "https://github.com/chjj/tty.js/issues" }, - "keywords": ["tty", "terminal", "term"], - "tags": ["tty", "terminal", "term"], + "keywords": ["tty", "terminal", "term", "xterm"], + "tags": ["tty", "terminal", "term", "xterm"], "dependencies": { "express": "3.1.0", "socket.io": "0.9.13", From 165f7083da8629e91cf5855ae8eeecad3addfb61 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 11 Aug 2013 05:19:09 -0500 Subject: [PATCH 114/131] remove unused code. --- static/tty.js | 1 - 1 file changed, 1 deletion(-) diff --git a/static/tty.js b/static/tty.js index 1ee7640d..1fa1b354 100644 --- a/static/tty.js +++ b/static/tty.js @@ -28,7 +28,6 @@ var initialTitle = document.title; */ var EventEmitter = Terminal.EventEmitter - , isMac = Terminal.isMac , inherits = Terminal.inherits , on = Terminal.on , off = Terminal.off From 217a449efae285a5f2fdc1e1f7db2d20df81ba2e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Tue, 13 Aug 2013 17:16:40 -0500 Subject: [PATCH 115/131] do not pollute Terminal object. --- lib/tty.js | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index 95a9b5e4..50d5aa38 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -142,7 +142,7 @@ Server.prototype.handleOptions = function(req, res, next) { }); } - res.send('Terminal.options = ' + res.send('Terminal._opts = ' + JSON.stringify(conf.term, null, 2) + ';\n' + '(' @@ -607,24 +607,24 @@ function sanitize(file) { function applyConfig() { var hasOwnProperty = Object.prototype.hasOwnProperty; - for (var key in Terminal.options) { - if (!hasOwnProperty.call(Terminal.options, key)) continue; - if (typeof Terminal.options[key] === 'object' && Terminal.options[key]) { + for (var key in Terminal._opts) { + if (!hasOwnProperty.call(Terminal._opts, key)) continue; + if (typeof Terminal._opts[key] === 'object' && Terminal._opts[key]) { if (!Terminal[key]) { - Terminal[key] = Terminal.options[key]; + Terminal[key] = Terminal._opts[key]; continue; } - for (var k in Terminal.options[key]) { - if (hasOwnProperty.call(Terminal.options[key], k)) { - Terminal[key][k] = Terminal.options[key][k]; + for (var k in Terminal._opts[key]) { + if (hasOwnProperty.call(Terminal._opts[key], k)) { + Terminal[key][k] = Terminal._opts[key][k]; } } } else { - Terminal[key] = Terminal.options[key]; + Terminal[key] = Terminal._opts[key]; } } - delete Terminal.options; + delete Terminal._opts; } /** From 243f02dd6fc05d32c1a2e99a25b739e60b937a87 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 16 Aug 2013 15:41:57 -0500 Subject: [PATCH 116/131] hook into term.js's prefix mode keys. --- lib/tty.js | 32 ++++++++++++++++++++ static/tty.js | 82 ++++++++++++++++----------------------------------- 2 files changed, 57 insertions(+), 57 deletions(-) diff --git a/lib/tty.js b/lib/tty.js index 50d5aa38..2c97cf28 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -208,6 +208,10 @@ Server.prototype.handleConnection = function(socket) { socket.on('disconnect', function() { return session.handleDisconnect(); }); + + socket.on('request paste', function(func) { + return session.handlePaste(func); + }); }; Server.prototype._basicAuth = function() { @@ -554,6 +558,34 @@ Session.prototype.handleDisconnect = function() { conf.sessionTimeout / 1000 / 60 | 0); }; +Session.prototype.handlePaste = function(func) { + var execFile = require('child_process').execFile; + + function exec(args) { + var file = args.shift(); + return execFile(file, args, function(err, stdout, stderr) { + if (err) return func(err); + if (stderr && !stdout) return func(new Error(stderr)); + return func(null, stdout); + }); + } + + // X11: + return exec(['xsel', '-o', '-p'], function(err, text) { + if (!err) return func(null, text); + return exec(['xclip', '-o', '-selection', 'primary'], function(err, text) { + if (!err) return func(null, text); + // Mac: + return exec(['pbpaste'], function(err, text) { + if (!err) return func(null, text); + // Windows: + // return exec(['sfk', 'fromclip'], function(err, text) { + return func(new Error('Failed to get clipboard contents.')); + }); + }); + }); +}; + Session.prototype.setTimeout = function(time, func) { this.clearTimeout(); this.timeout = setTimeout(func.bind(this), time); diff --git a/static/tty.js b/static/tty.js index 1fa1b354..f56170ea 100644 --- a/static/tty.js +++ b/static/tty.js @@ -719,24 +719,9 @@ Tab.prototype.destroy = function() { }; Tab.prototype.hookKeys = function() { - this.on('key', function(key, ev) { - // ^A for screen-key-like prefix. - if (Terminal.screenKeys) { - if (this.pendingKey) { - this._ignoreNext(); - this.pendingKey = false; - this.specialKeyHandler(key); - return; - } - - // ^A - if (key === '\x01') { - this._ignoreNext(); - this.pendingKey = true; - return; - } - } + var self = this; + this.on('key', function(key, ev) { // Alt-` to quickly swap between windows. if (key === '\x1b`') { var i = indexOf(tty.windows, this.window) + 1; @@ -768,48 +753,31 @@ Tab.prototype.hookKeys = function() { }.bind(this), 1); } }); -}; -//var keyDown = Tab.prototype.keyDown; -//Tab.prototype.keyDown = function(ev) { -// if (!Terminal.escapeKey) { -// return keyDown.apply(this, arguments); -// } -// if (ev.keyCode === Terminal.escapeKey) { -// return keyDown.call(this, { keyCode: 27 }); -// } -// return keyDown.apply(this, arguments); -//}; - -// tmux/screen-like keys -Tab.prototype.specialKeyHandler = function(key) { - var win = this.window; + this.on('request paste', function(key) { + this.socket.emit('request paste', function(err, text) { + if (err) return; + self.send(text); + }); + }); - switch (key) { - case '\x01': // ^A - this.send(key); - break; - case 'c': - win.createTab(); - break; - case 'k': - win.focused.destroy(); - break; - case 'w': // tmux - case '"': // screen - break; - default: - if (key >= '0' && key <= '9') { - key = +key; - // 1-indexed - key--; - if (!~key) key = 9; - if (win.tabs[key]) { - win.tabs[key].focus(); - } - } - break; - } + this.on('request create', function() { + this.window.createTab(); + }); + + this.on('request term', function(key) { + if (this.window.tabs[key]) { + this.window.tabs[key].focus(); + } + }); + + this.on('request term next', function(key) { + this.window.nextTab(); + }); + + this.on('request term previous', function(key) { + this.window.previousTab(); + }); }; Tab.prototype._ignoreNext = function() { From 3f28d549a0a47ea487e2cffd93494771c334db2e Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 18 Aug 2013 12:09:18 -0500 Subject: [PATCH 117/131] remove escapeKey option. --- lib/config.js | 11 ----------- static/tty.js | 7 ------- 2 files changed, 18 deletions(-) diff --git a/lib/config.js b/lib/config.js index eb8f3700..119fcd97 100644 --- a/lib/config.js +++ b/lib/config.js @@ -155,17 +155,6 @@ function checkConfig(conf) { conf.term.colors; // [] conf.term.programFeatures; // false - conf.term.escapeKey; // 192 - conf.escapeKey = conf.term.escapeKey || conf.escapeKey; - if (conf.escapeKey) { - conf.term.escapeKey = typeof conf.escapeKey === 'string' - ? conf.escapeKey - : '`'; - //conf.term.escapeKey = typeof conf.escapeKey === 'number' - // ? conf.escapeKey - // : 192; - } - conf.debug = conf.debug || conf.term.debug || false; conf.term.debug = conf.debug; // false diff --git a/static/tty.js b/static/tty.js index f56170ea..17226581 100644 --- a/static/tty.js +++ b/static/tty.js @@ -745,13 +745,6 @@ Tab.prototype.hookKeys = function() { this._ignoreNext(); return this.window.createTab(); } - - if (key === Terminal.escapeKey) { - this._ignoreNext(); - return setTimeout(function() { - this.keyDown({ keyCode: 27 }); - }.bind(this), 1); - } }); this.on('request paste', function(key) { From 95f756ac922aa4171cca488b3b595d62f9d37540 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 18 Aug 2013 14:12:01 -0500 Subject: [PATCH 118/131] remove old window focus shortcuts. --- static/tty.js | 43 +++++++++++++++++++++++++------------------ 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/static/tty.js b/static/tty.js index 17226581..45af0bff 100644 --- a/static/tty.js +++ b/static/tty.js @@ -721,30 +721,37 @@ Tab.prototype.destroy = function() { Tab.prototype.hookKeys = function() { var self = this; + // Alt-[jk] to quickly swap between windows. this.on('key', function(key, ev) { - // Alt-` to quickly swap between windows. - if (key === '\x1b`') { - var i = indexOf(tty.windows, this.window) + 1; + if (Terminal.focusKeys === false) { + return; + } - this._ignoreNext(); - if (tty.windows[i]) return tty.windows[i].highlight(); - if (tty.windows[0]) return tty.windows[0].highlight(); + var offset + , i; - return this.window.highlight(); + if (key === '\x1bj') { + offset = -1; + } else if (key === '\x1bk') { + offset = +1; + } else { + return; } - // URXVT Keys for tab navigation and creation. - // Shift-Left, Shift-Right, Shift-Down - if (key === '\x1b[1;2D') { - this._ignoreNext(); - return this.window.previousTab(); - } else if (key === '\x1b[1;2B') { - this._ignoreNext(); - return this.window.nextTab(); - } else if (key === '\x1b[1;2C') { - this._ignoreNext(); - return this.window.createTab(); + i = indexOf(tty.windows, this.window) + offset; + + this._ignoreNext(); + + if (tty.windows[i]) return tty.windows[i].highlight(); + + if (offset > 0) { + if (tty.windows[0]) return tty.windows[0].highlight(); + } else { + i = tty.windows.length - 1; + if (tty.windows[i]) return tty.windows[i].highlight(); } + + return this.window.highlight(); }); this.on('request paste', function(key) { From 1af09c7283f4e8d2a12b45e3668dbeed2707784b Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sat, 24 Aug 2013 23:45:00 -0500 Subject: [PATCH 119/131] v0.2.13 --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index a5083ce8..86e1588d 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.12-2", + "version": "0.2.13", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", @@ -16,7 +16,7 @@ "express": "3.1.0", "socket.io": "0.9.13", "pty.js": "0.2.3", - "term.js": "0.0.2" + "term.js": "0.0.3" }, "engines": { "node": ">= 0.8.0" } } From 11aea19100ac2f9c245ff4e2d20d0203c2025209 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Fri, 8 Nov 2013 13:38:35 -0600 Subject: [PATCH 120/131] use express 3.4.4 and socket.io 0.9.16. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 86e1588d..809c0b14 100644 --- a/package.json +++ b/package.json @@ -13,8 +13,8 @@ "keywords": ["tty", "terminal", "term", "xterm"], "tags": ["tty", "terminal", "term", "xterm"], "dependencies": { - "express": "3.1.0", - "socket.io": "0.9.13", + "express": "3.4.4", + "socket.io": "0.9.16", "pty.js": "0.2.3", "term.js": "0.0.3" }, From 8f518e122a7d19e8c335ac2e890eb2e7171c83e8 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Dec 2013 06:29:36 -0600 Subject: [PATCH 121/131] add CLA to readme. --- README.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/README.md b/README.md index 74a751c0..fd72fa3f 100644 --- a/README.md +++ b/README.md @@ -163,6 +163,12 @@ The distance to go before full xterm compatibility. - Origin Mode, Insert Mode - Proper Tab Setting +### Contribution and License Agreement + +If you contribute code to marked, you are implicitly allowing your code to be +distributed under the MIT license. You are also implicitly verifying that all +code is your original work. `` + ## License Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) From 2ef9e0ba2545e3e2308c3678704ce80565aba9e6 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 4 Dec 2013 06:37:58 -0600 Subject: [PATCH 122/131] fix CLA. --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index fd72fa3f..aa0853d1 100644 --- a/README.md +++ b/README.md @@ -165,9 +165,9 @@ The distance to go before full xterm compatibility. ### Contribution and License Agreement -If you contribute code to marked, you are implicitly allowing your code to be -distributed under the MIT license. You are also implicitly verifying that all -code is your original work. `` +If you contribute code to this project, you are implicitly allowing your code +to be distributed under the MIT license. You are also implicitly verifying that +all code is your original work. `` ## License From 0cee00a7010028dc7f908a921f4161d873f70c17 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 2 Mar 2014 23:31:58 -0600 Subject: [PATCH 123/131] misc. comments. --- README.md | 4 ++-- bin/tty.js | 5 +++++ lib/config.js | 2 +- lib/logger.js | 2 +- lib/tty.js | 2 +- 5 files changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index aa0853d1..ca237421 100644 --- a/README.md +++ b/README.md @@ -163,7 +163,7 @@ The distance to go before full xterm compatibility. - Origin Mode, Insert Mode - Proper Tab Setting -### Contribution and License Agreement +## Contribution and License Agreement If you contribute code to this project, you are implicitly allowing your code to be distributed under the MIT license. You are also implicitly verifying that @@ -171,6 +171,6 @@ all code is your original work. `` ## License -Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) +Copyright (c) 2012-2014, Christopher Jeffrey (MIT License) [1]: http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking diff --git a/bin/tty.js b/bin/tty.js index e1c35ad5..8b4e1441 100755 --- a/bin/tty.js +++ b/bin/tty.js @@ -1,5 +1,10 @@ #!/usr/bin/env node +/** + * tty.js + * Copyright (c) 2012-2014, Christopher Jeffrey (MIT License) + */ + process.title = 'tty.js'; var tty = require('../'); diff --git a/lib/config.js b/lib/config.js index 119fcd97..cdc1bc35 100644 --- a/lib/config.js +++ b/lib/config.js @@ -1,6 +1,6 @@ /** * tty.js: config.js - * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * Copyright (c) 2012-2014, Christopher Jeffrey (MIT License) */ var path = require('path') diff --git a/lib/logger.js b/lib/logger.js index f1f8843c..4349b021 100644 --- a/lib/logger.js +++ b/lib/logger.js @@ -1,6 +1,6 @@ /** * tty.js: logger.js - * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * Copyright (c) 2012-2014, Christopher Jeffrey (MIT License) */ var slice = Array.prototype.slice diff --git a/lib/tty.js b/lib/tty.js index 2c97cf28..78849eb8 100644 --- a/lib/tty.js +++ b/lib/tty.js @@ -1,6 +1,6 @@ /** * tty.js - * Copyright (c) 2012-2013, Christopher Jeffrey (MIT License) + * Copyright (c) 2012-2014, Christopher Jeffrey (MIT License) */ /** From dce1023ea004046c31ec687633d2d626d481f7d1 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Thu, 6 Mar 2014 14:14:09 -0600 Subject: [PATCH 124/131] use pty.js v0.2.4. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 809c0b14..493a144c 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "express": "3.4.4", "socket.io": "0.9.16", - "pty.js": "0.2.3", + "pty.js": "0.2.4", "term.js": "0.0.3" }, "engines": { "node": ">= 0.8.0" } From fd11e8ef17e3afc7e95c59a326113dbffb6e1079 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 29 Mar 2015 04:39:37 -0700 Subject: [PATCH 125/131] use updated pty.js and term.js. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 493a144c..93195ccc 100644 --- a/package.json +++ b/package.json @@ -15,8 +15,8 @@ "dependencies": { "express": "3.4.4", "socket.io": "0.9.16", - "pty.js": "0.2.4", - "term.js": "0.0.3" + "pty.js": "0.2.7", + "term.js": "0.0.4" }, "engines": { "node": ">= 0.8.0" } } From df25b1dc9ca519dea5af9acaf7b1f77da128e0f5 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Sun, 29 Mar 2015 04:39:49 -0700 Subject: [PATCH 126/131] v0.2.14 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 93195ccc..f34897ea 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.13", + "version": "0.2.14", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From 83a516c7e5a41d4b62d718d25b0c11a09d4ea492 Mon Sep 17 00:00:00 2001 From: Pagan Gazzard Date: Wed, 1 Apr 2015 14:19:05 +0100 Subject: [PATCH 127/131] Fix issue with pty.js 0.2.7 having been removed from npm. --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index f34897ea..98ae7a29 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "dependencies": { "express": "3.4.4", "socket.io": "0.9.16", - "pty.js": "0.2.7", + "pty.js": "0.2.7-1", "term.js": "0.0.4" }, "engines": { "node": ">= 0.8.0" } From aa25ee499d67a182925def352958c7d0e6b3ac57 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 1 Apr 2015 06:39:30 -0700 Subject: [PATCH 128/131] v0.2.14-1 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 98ae7a29..c474a0c9 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.14", + "version": "0.2.14-1", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From 44d39b274f838f9cf92b18a15040e29732f125ad Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 5 Aug 2015 23:24:43 -0700 Subject: [PATCH 129/131] package: license. --- package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/package.json b/package.json index c474a0c9..9c048458 100644 --- a/package.json +++ b/package.json @@ -3,6 +3,7 @@ "description": "A terminal for your browser", "author": "Christopher Jeffrey", "version": "0.2.14-1", + "license": "MIT", "main": "./index.js", "bin": "./bin/tty.js", "man": "./man/tty.js.1", From 383d43928540d2e68b5db071e8d70b9591c1b678 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 5 Aug 2015 23:26:19 -0700 Subject: [PATCH 130/131] update pty.js and term.js. fixes #146. --- package.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 9c048458..2052634b 100644 --- a/package.json +++ b/package.json @@ -16,8 +16,8 @@ "dependencies": { "express": "3.4.4", "socket.io": "0.9.16", - "pty.js": "0.2.7-1", - "term.js": "0.0.4" + "pty.js": ">= 0.2.13", + "term.js": ">= 0.0.5" }, "engines": { "node": ">= 0.8.0" } } From 1996ff663a2794966c3690af85d7758b510f0341 Mon Sep 17 00:00:00 2001 From: Christopher Jeffrey Date: Wed, 5 Aug 2015 23:26:54 -0700 Subject: [PATCH 131/131] v0.2.15 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2052634b..73f4c7cd 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "tty.js", "description": "A terminal for your browser", "author": "Christopher Jeffrey", - "version": "0.2.14-1", + "version": "0.2.15", "license": "MIT", "main": "./index.js", "bin": "./bin/tty.js",