This commit is contained in:
Florian Mounier
2014-02-13 14:45:04 +01:00
parent d986ee8ba7
commit c69779884f
8 changed files with 1440 additions and 1477 deletions

View File

@@ -151,10 +151,6 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
self.log.debug('Adding handler')
fcntl.fcntl(self.fd, fcntl.F_SETFL, os.O_NONBLOCK)
# Set the size of the terminal window:
s = struct.pack("HHHH", 80, 80, 0, 0)
fcntl.ioctl(self.fd, termios.TIOCSWINSZ, s)
def utf8_error(e):
self.log.error(e)
@@ -202,16 +198,14 @@ class TermWebSocket(Route, tornado.websocket.WebSocketHandler):
self.pty()
def on_message(self, message):
if message.startswith('RS|'):
message = message[3:]
cols, rows = map(int, message.split(','))
if message[0] == 'R':
cols, rows = map(int, message[1:].split(','))
s = struct.pack("HHHH", rows, cols, 0, 0)
fcntl.ioctl(self.fd, termios.TIOCSWINSZ, s)
self.log.info('SIZE (%d, %d)' % (cols, rows))
elif message.startswith('SH|'):
message = message[3:]
elif message[0] == 'S':
self.log.info('WRIT<%r' % message)
self.writer.write(message)
self.writer.write(message[1:])
self.writer.flush()
def shell_handler(self, fd, events):

View File

@@ -15,25 +15,26 @@
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
term = ws = null
cols = rows = null
quit = false
$ = document.querySelectorAll.bind(document)
send = (data) ->
ws.send 'S' + data
ctl = (type, args...) ->
params = args.join(',')
if type == 'Resize'
ws.send 'R' + params
ws_url = 'ws://' + document.location.host + '/ws' + location.pathname
ws = new WebSocket ws_url
term = new Terminal $('#wrapper')[0], send, ctl
ws.onopen = ->
console.log "WebSocket open", arguments
term = new Terminal (data) -> ws.send 'SH|' + data
term.open $('main')[0]
$('.terminal')[0].style = ''
resize()
ws.send 'R' + term.cols + ',' + term.rows
ws.onerror = -> console.log "WebSocket error", arguments
ws.onmessage = (e) ->
@@ -51,32 +52,6 @@ addEventListener 'beforeunload', ->
if not quit
'This will exit the terminal session'
addEventListener 'resize', resize = ->
main = $('main')[0]
fake_term = document.createElement('div')
fake_term.className = 'terminal test'
fake_term_div = document.createElement('div')
fake_term_line = document.createElement('span')
fake_term_line.textContent = '0123456789'
fake_term_div.appendChild(fake_term_line)
fake_term.appendChild(fake_term_div)
main.appendChild(fake_term)
ew = fake_term_line.getBoundingClientRect().width
eh = fake_term_div.getBoundingClientRect().height
main.removeChild(fake_term)
main_bb = main.getBoundingClientRect()
cols = Math.floor(10 * main_bb.width / ew) - 1
rows = Math.floor(main_bb.height / eh)
console.log "Computed #{cols} cols and #{rows} rows from ", main_bb, ew, eh
term.resize cols, rows
for div in $('.terminal div')
div.style.height = eh + 'px'
ws.send "RS|#{cols},#{rows}"
bench = (n=100000000) ->
rnd = ''
while rnd.length < n

View File

@@ -47,9 +47,40 @@ State =
class Terminal
constructor: (@out) ->
@cols = 80
@rows = 24
constructor: (@parent, @out, @ctl=->) ->
# Global elements
@context = @parent.ownerDocument.defaultView
@document = @parent.ownerDocument
@body = @document.getElementsByTagName('body')[0]
# Main terminal element
@element = @document.createElement('div')
@element.className = 'terminal focus'
@element.style.outline = 'none'
@element.setAttribute 'tabindex', 0
@parent.appendChild(@element)
# Adding one line to compute char size
div = @document.createElement('div')
div.className = 'line'
@element.appendChild(div)
@children = [div]
@compute_char_size()
div.style.height = @char_size.height + 'px'
term_size = @parent.getBoundingClientRect()
@cols = Math.floor(term_size.width / @char_size.width) - 1 # ?
@rows = Math.floor(term_size.height / @char_size.height)
i = @rows - 1
while i--
div = @document.createElement('div')
div.style.height = @char_size.height + 'px'
div.className = 'line'
@element.appendChild(div)
@children.push(div)
@scrollback = 100000
@visualBell = 100
@@ -58,9 +89,24 @@ class Terminal
@cursorBlink = true
@screenKeys = false
@cursorState = 0
@init()
init: ->
@reset_vars()
# Draw screen
@refresh 0, @rows - 1
@focus()
@startBlink()
addEventListener 'keydown', @keyDown.bind(@)
addEventListener 'keypress', @keyPress.bind(@)
addEventListener 'focus', @focus.bind(@)
addEventListener 'blur', @blur.bind(@)
addEventListener 'paste', @paste.bind(@)
addEventListener 'resize', @resize.bind(@)
@initmouse()
reset_vars: ->
@ybase = 0
@ydisp = 0
@x = 0
@@ -96,6 +142,15 @@ class Terminal
@setupStops()
@skipNextKey = false
compute_char_size: ->
test_span = document.createElement('span')
test_span.textContent = '0123456789'
@children[0].appendChild(test_span)
@char_size =
width: test_span.getBoundingClientRect().width / 10
height: @children[0].getBoundingClientRect().height
@children[0].removeChild(test_span)
eraseAttr: ->
(@defAttr & ~0x1ff) | (@curAttr & 0x1ff)
@@ -119,44 +174,6 @@ class Terminal
@send @context.clipboardData.getData('Text')
cancel(ev)
open: (parent) ->
@parent = parent or @parent
throw new Error('Terminal requires a parent element') unless @parent
# Global elements
@context = @parent.ownerDocument.defaultView
@document = @parent.ownerDocument
@body = @document.getElementsByTagName('body')[0]
# Main terminal element
@element = @document.createElement('div')
@element.className = 'terminal focus'
@element.style.outline = 'none'
@element.setAttribute('tabindex', 0)
# Terminal lines
@children = [];
i = @rows
while i--
div = @document.createElement('div')
@element.appendChild(div)
@children.push(div)
@parent.appendChild(@element);
# Draw screen
@refresh 0, @rows - 1
@focus()
@startBlink()
addEventListener('keydown', @keyDown.bind(@))
addEventListener('keypress', @keyPress.bind(@))
addEventListener('focus', @focus.bind(@))
addEventListener('blur', @blur.bind(@))
addEventListener('paste', @paste.bind(@))
@initmouse()
# XTerm mouse events
# http://invisible-island.net/xterm/ctlseqs/ctlseqs.html#Mouse%20Tracking
# To better understand these
@@ -350,7 +367,7 @@ class Terminal
refresh: (start, end) ->
if end - start >= @rows / 2
if end - start >= @rows / 3
parent = @element.parentNode
parent?.removeChild @element
@@ -487,7 +504,6 @@ class Terminal
@ydisp = @ybase
@lines.splice @ybase + @scrollTop, 1
# @maxRange();
@updateRange @scrollTop
@updateRange @scrollBottom
@@ -706,7 +722,7 @@ class Terminal
@state = State.normal
else
@state = State.normal
@error "Unknown ESC control: %s.", ch
console.log "Unknown ESC control:", ch
when State.charset
switch ch
@@ -1362,50 +1378,58 @@ class Terminal
@element.classList.remove "bell"
), @visualBell
resize: (x, y) ->
x = 1 if x < 1
y = 1 if y < 1
resize: ->
old_cols = @cols
old_rows = @rows
term_size = @parent.getBoundingClientRect()
@cols = Math.floor(term_size.width / @char_size.width) - 1 # ?
@rows = Math.floor(term_size.height / @char_size.height)
if old_cols == @cols and old_rows == @rows
return
@ctl 'Resize', @cols, @rows
# resize cols
j = @cols
if j < x
if old_cols < @cols
# does xterm use the default attr?
ch = [@defAttr, " "]
i = @lines.length
while i--
@lines[i].push ch while @lines[i].length < x
else if j > x
@lines[i].push ch while @lines[i].length < @cols
else if old_cols > @cols
i = @lines.length
while i--
@lines[i].pop() while @lines[i].length > x
@lines[i].pop() while @lines[i].length > @cols
@setupStops j
@cols = x
@setupStops old_cols
# resize rows
j = @rows
if j < y
j = old_rows
if j < @rows
el = @element
while j++ < y
while j++ < @rows
@lines.push @blankLine() if @lines.length < y + @ybase
if @children.length < y
if @children.length < @rows
line = @document.createElement("div")
@line.className = 'line'
line.style.height = @char_size.height + 'px'
el.appendChild line
@children.push line
else if j > y
while j-- > y
@lines.pop() if @lines.length > y + @ybase
if @children.length > y
else if j > @rows
while j-- > @rows
@lines.pop() if @lines.length > @rows + @ybase
if @children.length > @rows
el = @children.pop()
continue unless el
el.parentNode.removeChild el
@rows = y
# make sure the cursor stays on screen
@y = y - 1 if @y >= y
@x = x - 1 if @x >= x
@y = @rows - 1 if @y >= @rows
@x = @cols - 1 if @x >= @cols
@scrollTop = 0
@scrollBottom = y - 1
@scrollBottom = @rows - 1
@refresh 0, @rows - 1
# it's a real nightmare trying
@@ -1520,7 +1544,7 @@ class Terminal
# ESC c Full Reset (RIS).
reset: ->
@init()
@reset_vars()
@refresh 0, @rows - 1
# ESC H Tab Set (HTS is 0x88).
@@ -2907,12 +2931,12 @@ class Terminal
get_html_height_in_lines: (html) ->
line_height = +@children[0].style.height.replace("px", "")
temp_node = document.createElement("div")
temp_node.innerHTML = html
@element.appendChild temp_node
html_height = temp_node.getBoundingClientRect().height
@element.removeChild temp_node
Math.ceil(html_height / @char_size.height)
# DEC Special Character and Line Drawing Set.
# http://vt100.net/docs/vt102-ug/table5-13.html

View File

@@ -1,180 +1,6 @@
// Generated by CoffeeScript 1.6.3
var $, State, Terminal, alt, bench, cancel, cbench, cols, ctrl, e, first, quit, resize, rows, s, state, term, virtual_input, ws, ws_url;
term = ws = null;
cols = rows = null;
quit = false;
$ = document.querySelectorAll.bind(document);
ws_url = 'ws://' + document.location.host + '/ws' + location.pathname;
ws = new WebSocket(ws_url);
ws.onopen = function() {
console.log("WebSocket open", arguments);
term = new Terminal(function(data) {
return ws.send('SH|' + data);
});
term.open($('main')[0]);
$('.terminal')[0].style = '';
return resize();
};
ws.onerror = function() {
return console.log("WebSocket error", arguments);
};
ws.onmessage = function(e) {
return setTimeout(function() {
return term.write(e.data);
}, 1);
};
ws.onclose = function() {
console.log("WebSocket closed", arguments);
quit = true;
return open('', '_self').close();
};
addEventListener('beforeunload', function() {
if (!quit) {
return 'This will exit the terminal session';
}
});
addEventListener('resize', resize = function() {
var div, eh, ew, fake_term, fake_term_div, fake_term_line, main, main_bb, _i, _len, _ref;
main = $('main')[0];
fake_term = document.createElement('div');
fake_term.className = 'terminal test';
fake_term_div = document.createElement('div');
fake_term_line = document.createElement('span');
fake_term_line.textContent = '0123456789';
fake_term_div.appendChild(fake_term_line);
fake_term.appendChild(fake_term_div);
main.appendChild(fake_term);
ew = fake_term_line.getBoundingClientRect().width;
eh = fake_term_div.getBoundingClientRect().height;
main.removeChild(fake_term);
main_bb = main.getBoundingClientRect();
cols = Math.floor(10 * main_bb.width / ew) - 1;
rows = Math.floor(main_bb.height / eh);
console.log("Computed " + cols + " cols and " + rows + " rows from ", main_bb, ew, eh);
term.resize(cols, rows);
_ref = $('.terminal div');
for (_i = 0, _len = _ref.length; _i < _len; _i++) {
div = _ref[_i];
div.style.height = eh + 'px';
}
return ws.send("RS|" + cols + "," + rows);
});
bench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars in " + ((new Date()).getTime() - t0) + " ms");
};
cbench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += "\x1b[" + (30 + parseInt(Math.random() * 20)) + "m";
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars + colors in " + ((new Date()).getTime() - t0) + " ms");
};
try {
document.createEvent("TouchEvent");
virtual_input = true;
} catch (_error) {
e = _error;
virtual_input = false;
}
if (virtual_input) {
ctrl = false;
alt = false;
first = true;
virtual_input = document.createElement('input');
virtual_input.type = 'password';
virtual_input.style.position = 'fixed';
virtual_input.style.top = 0;
virtual_input.style.left = 0;
virtual_input.style.border = 'none';
virtual_input.style.outline = 'none';
virtual_input.style.opacity = 0;
virtual_input.value = '0';
document.body.appendChild(virtual_input);
virtual_input.addEventListener('blur', function() {
var _this = this;
return setTimeout((function() {
return _this.focus();
}), 10);
});
addEventListener('click', function() {
return virtual_input.focus();
});
addEventListener('touchstart', function(e) {
if (e.touches.length === 1) {
return ctrl = true;
} else if (e.touches.length === 2) {
ctrl = false;
return alt = true;
} else if (e.touches.length === 3) {
ctrl = true;
return alt = true;
}
});
virtual_input.addEventListener('keydown', function(e) {
term.keyDown(e);
return true;
});
virtual_input.addEventListener('input', function(e) {
var len;
len = this.value.length;
if (len === 0) {
e.keyCode = 8;
term.keyDown(e);
this.value = '0';
return true;
}
e.keyCode = this.value.charAt(1).charCodeAt(0);
if ((ctrl || alt) && !first) {
e.keyCode = this.value.charAt(1).charCodeAt(0);
e.ctrlKey = ctrl;
e.altKey = alt;
if (e.keyCode >= 97 && e.keyCode <= 122) {
e.keyCode -= 32;
}
term.keyDown(e);
this.value = '0';
ctrl = alt = false;
return true;
}
term.keyPress(e);
first = false;
this.value = '0';
return true;
});
}
var $, State, Terminal, alt, bench, cancel, cbench, cols, ctl, ctrl, e, first, quit, rows, s, send, state, term, virtual_input, ws, ws_url,
__slice = [].slice;
cancel = function(ev) {
if (ev.preventDefault) {
@@ -200,10 +26,36 @@ State = {
};
Terminal = (function() {
function Terminal(out) {
function Terminal(parent, out, ctl) {
var div, i, term_size;
this.parent = parent;
this.out = out;
this.cols = 80;
this.rows = 24;
this.ctl = ctl != null ? ctl : function() {};
this.context = this.parent.ownerDocument.defaultView;
this.document = this.parent.ownerDocument;
this.body = this.document.getElementsByTagName('body')[0];
this.element = this.document.createElement('div');
this.element.className = 'terminal focus';
this.element.style.outline = 'none';
this.element.setAttribute('tabindex', 0);
this.parent.appendChild(this.element);
div = this.document.createElement('div');
div.className = 'line';
this.element.appendChild(div);
this.children = [div];
this.compute_char_size();
div.style.height = this.char_size.height + 'px';
term_size = this.parent.getBoundingClientRect();
this.cols = Math.floor(term_size.width / this.char_size.width) - 1;
this.rows = Math.floor(term_size.height / this.char_size.height);
i = this.rows - 1;
while (i--) {
div = this.document.createElement('div');
div.style.height = this.char_size.height + 'px';
div.className = 'line';
this.element.appendChild(div);
this.children.push(div);
}
this.scrollback = 100000;
this.visualBell = 100;
this.convertEol = false;
@@ -211,10 +63,20 @@ Terminal = (function() {
this.cursorBlink = true;
this.screenKeys = false;
this.cursorState = 0;
this.init();
this.reset_vars();
this.refresh(0, this.rows - 1);
this.focus();
this.startBlink();
addEventListener('keydown', this.keyDown.bind(this));
addEventListener('keypress', this.keyPress.bind(this));
addEventListener('focus', this.focus.bind(this));
addEventListener('blur', this.blur.bind(this));
addEventListener('paste', this.paste.bind(this));
addEventListener('resize', this.resize.bind(this));
this.initmouse();
}
Terminal.prototype.init = function() {
Terminal.prototype.reset_vars = function() {
var i;
this.ybase = 0;
this.ydisp = 0;
@@ -248,6 +110,18 @@ Terminal = (function() {
return this.skipNextKey = false;
};
Terminal.prototype.compute_char_size = function() {
var test_span;
test_span = document.createElement('span');
test_span.textContent = '0123456789';
this.children[0].appendChild(test_span);
this.char_size = {
width: test_span.getBoundingClientRect().width / 10,
height: this.children[0].getBoundingClientRect().height
};
return this.children[0].removeChild(test_span);
};
Terminal.prototype.eraseAttr = function() {
return (this.defAttr & ~0x1ff) | (this.curAttr & 0x1ff);
};
@@ -280,38 +154,6 @@ Terminal = (function() {
return cancel(ev);
};
Terminal.prototype.open = function(parent) {
var div, i;
this.parent = parent || this.parent;
if (!this.parent) {
throw new Error('Terminal requires a parent element');
}
this.context = this.parent.ownerDocument.defaultView;
this.document = this.parent.ownerDocument;
this.body = this.document.getElementsByTagName('body')[0];
this.element = this.document.createElement('div');
this.element.className = 'terminal focus';
this.element.style.outline = 'none';
this.element.setAttribute('tabindex', 0);
this.children = [];
i = this.rows;
while (i--) {
div = this.document.createElement('div');
this.element.appendChild(div);
this.children.push(div);
}
this.parent.appendChild(this.element);
this.refresh(0, this.rows - 1);
this.focus();
this.startBlink();
addEventListener('keydown', this.keyDown.bind(this));
addEventListener('keypress', this.keyPress.bind(this));
addEventListener('focus', this.focus.bind(this));
addEventListener('blur', this.blur.bind(this));
addEventListener('paste', this.paste.bind(this));
return this.initmouse();
};
Terminal.prototype.initmouse = function() {
var encode, getButton, getCoords, pressed, sendButton, sendEvent, sendMove,
_this = this;
@@ -388,7 +230,7 @@ Terminal = (function() {
return _this.send("\x1b[M" + String.fromCharCode.apply(String, data));
};
getButton = function(ev) {
var button, meta, mod, shift;
var button, ctrl, meta, mod, shift;
switch (ev.type) {
case "mousedown":
button = ev.button != null ? +ev.button : (ev.which != null ? ev.which - 1 : null);
@@ -492,7 +334,7 @@ Terminal = (function() {
Terminal.prototype.refresh = function(start, end) {
var attr, bg, ch, classes, data, fg, flags, i, line, out, parent, row, width, x, y;
if (end - start >= this.rows / 2) {
if (end - start >= this.rows / 3) {
parent = this.element.parentNode;
if (parent != null) {
parent.removeChild(this.element);
@@ -501,7 +343,6 @@ Terminal = (function() {
width = this.cols;
y = start;
if (end >= this.lines.length) {
this.log("`end` is too large. Most likely a bad CSR.");
end = this.lines.length - 1;
}
while (y <= end) {
@@ -573,7 +414,7 @@ Terminal = (function() {
if (ch <= " ") {
out += "&nbsp;";
} else {
if (ch > "＀" && (ch >= "" && ch <= "ᄒ") || (ch >= "ᅡ" && ch <= "ᅦ") || (ch >= "ᅧ" && ch <= "ᅬ") || (ch >= "ᅭ" && ch <= "ᅲ") || (ch >= "ᅳ" && ch <= "ᅵ") || (ch >= "¢" && ch <= "₩") || (ch >= "" && ch <= "")) {
if (("\uff00" < ch && ch < "\uffef")) {
i++;
}
out += ch;
@@ -738,7 +579,7 @@ Terminal = (function() {
this.lines[this.y + this.ybase][this.x] = [this.curAttr, ch];
this.x++;
this.updateRange(this.y);
if (ch > "＀" && (ch >= "" && ch <= "ᄒ") || (ch >= "ᅡ" && ch <= "ᅦ") || (ch >= "ᅧ" && ch <= "ᅬ") || (ch >= "ᅭ" && ch <= "ᅲ") || (ch >= "ᅳ" && ch <= "ᅵ") || (ch >= "¢" && ch <= "₩") || (ch >= "" && ch <= "")) {
if (("\uff00" < ch && ch < "\uffef")) {
j = this.y + this.ybase;
if (this.cols < 2 || this.x >= this.cols) {
this.lines[j][this.x - 1] = [this.curAttr, " "];
@@ -860,7 +701,7 @@ Terminal = (function() {
break;
default:
this.state = State.normal;
this.error("Unknown ESC control: %s.", ch);
console.log("Unknown ESC control:", ch);
}
break;
case State.charset:
@@ -1130,8 +971,6 @@ Terminal = (function() {
}
this.send("\x1bP" + +valid + "$r" + pt + "\x1b\\");
break;
case "+p":
break;
case "+q":
pt = this.currentParam;
valid = false;
@@ -1463,76 +1302,55 @@ Terminal = (function() {
}), this.visualBell);
};
Terminal.prototype.log = function() {
var args;
if (!this.debug) {
Terminal.prototype.resize = function() {
var ch, el, i, j, line, old_cols, old_rows, term_size;
old_cols = this.cols;
old_rows = this.rows;
term_size = this.parent.getBoundingClientRect();
this.cols = Math.floor(term_size.width / this.char_size.width) - 1;
this.rows = Math.floor(term_size.height / this.char_size.height);
if (old_cols === this.cols && old_rows === this.rows) {
return;
}
if (!this.context.console || !this.context.console.log) {
return;
}
args = Array.prototype.slice.call(arguments);
return this.context.console.log.apply(this.context.console, args);
};
Terminal.prototype.error = function() {
var args;
if (!this.debug) {
return;
}
if (!this.context.console || !this.context.console.error) {
return;
}
args = Array.prototype.slice.call(arguments);
return this.context.console.error.apply(this.console.console, args);
};
Terminal.prototype.resize = function(x, y) {
var ch, el, i, j, line;
if (x < 1) {
x = 1;
}
if (y < 1) {
y = 1;
}
j = this.cols;
if (j < x) {
this.ctl('Resize', this.cols, this.rows);
if (old_cols < this.cols) {
ch = [this.defAttr, " "];
i = this.lines.length;
while (i--) {
while (this.lines[i].length < x) {
while (this.lines[i].length < this.cols) {
this.lines[i].push(ch);
}
}
} else if (j > x) {
} else if (old_cols > this.cols) {
i = this.lines.length;
while (i--) {
while (this.lines[i].length > x) {
while (this.lines[i].length > this.cols) {
this.lines[i].pop();
}
}
}
this.setupStops(j);
this.cols = x;
j = this.rows;
if (j < y) {
this.setupStops(old_cols);
j = old_rows;
if (j < this.rows) {
el = this.element;
while (j++ < y) {
while (j++ < this.rows) {
if (this.lines.length < y + this.ybase) {
this.lines.push(this.blankLine());
}
if (this.children.length < y) {
if (this.children.length < this.rows) {
line = this.document.createElement("div");
this.line.className = 'line';
line.style.height = this.char_size.height + 'px';
el.appendChild(line);
this.children.push(line);
}
}
} else if (j > y) {
while (j-- > y) {
if (this.lines.length > y + this.ybase) {
} else if (j > this.rows) {
while (j-- > this.rows) {
if (this.lines.length > this.rows + this.ybase) {
this.lines.pop();
}
if (this.children.length > y) {
if (this.children.length > this.rows) {
el = this.children.pop();
if (!el) {
continue;
@@ -1541,15 +1359,14 @@ Terminal = (function() {
}
}
}
this.rows = y;
if (this.y >= y) {
this.y = y - 1;
if (this.y >= this.rows) {
this.y = this.rows - 1;
}
if (this.x >= x) {
this.x = x - 1;
if (this.x >= this.cols) {
this.x = this.cols - 1;
}
this.scrollTop = 0;
this.scrollBottom = y - 1;
this.scrollBottom = this.rows - 1;
this.refresh(0, this.rows - 1);
return this.normal = null;
};
@@ -1705,7 +1522,7 @@ Terminal = (function() {
};
Terminal.prototype.reset = function() {
this.init();
this.reset_vars();
return this.refresh(0, this.rows - 1);
};
@@ -2164,7 +1981,6 @@ Terminal = (function() {
case 7:
return this.wraparoundMode = true;
case 66:
this.log("Serial port requested application keypad.");
return this.applicationKeypad = true;
case 9:
case 1000:
@@ -2174,8 +1990,7 @@ Terminal = (function() {
this.vt200Mouse = params === 1000;
this.normalMouse = params > 1000;
this.mouseEvents = true;
this.element.style.cursor = "default";
return this.log("Binding to mouse events.");
return this.element.style.cursor = "default";
case 1004:
return this.sendFocus = true;
case 1005:
@@ -2233,7 +2048,6 @@ Terminal = (function() {
case 7:
return this.wraparoundMode = false;
case 66:
this.log("Switching back to normal keypad.");
return this.applicationKeypad = false;
case 9:
case 1000:
@@ -2526,13 +2340,13 @@ Terminal = (function() {
};
Terminal.prototype.get_html_height_in_lines = function(html) {
var html_height, line_height, temp_node;
line_height = +this.children[0].style.height.replace("px", "");
var html_height, temp_node;
temp_node = document.createElement("div");
temp_node.innerHTML = html;
this.element.appendChild(temp_node);
html_height = temp_node.getBoundingClientRect().height;
return this.element.removeChild(temp_node);
this.element.removeChild(temp_node);
return Math.ceil(html_height / this.char_size.height);
};
Terminal.prototype.charsets = {
@@ -2620,3 +2434,159 @@ document.addEventListener('keydown', function(e) {
return state.x = state.y = null;
}
});
try {
document.createEvent("TouchEvent");
virtual_input = true;
} catch (_error) {
e = _error;
virtual_input = false;
}
if (virtual_input) {
ctrl = false;
alt = false;
first = true;
virtual_input = document.createElement('input');
virtual_input.type = 'password';
virtual_input.style.position = 'fixed';
virtual_input.style.top = 0;
virtual_input.style.left = 0;
virtual_input.style.border = 'none';
virtual_input.style.outline = 'none';
virtual_input.style.opacity = 0;
virtual_input.value = '0';
document.body.appendChild(virtual_input);
virtual_input.addEventListener('blur', function() {
var _this = this;
return setTimeout((function() {
return _this.focus();
}), 10);
});
addEventListener('click', function() {
return virtual_input.focus();
});
addEventListener('touchstart', function(e) {
if (e.touches.length === 1) {
return ctrl = true;
} else if (e.touches.length === 2) {
ctrl = false;
return alt = true;
} else if (e.touches.length === 3) {
ctrl = true;
return alt = true;
}
});
virtual_input.addEventListener('keydown', function(e) {
term.keyDown(e);
return true;
});
virtual_input.addEventListener('input', function(e) {
var len;
len = this.value.length;
if (len === 0) {
e.keyCode = 8;
term.keyDown(e);
this.value = '0';
return true;
}
e.keyCode = this.value.charAt(1).charCodeAt(0);
if ((ctrl || alt) && !first) {
e.keyCode = this.value.charAt(1).charCodeAt(0);
e.ctrlKey = ctrl;
e.altKey = alt;
if (e.keyCode >= 97 && e.keyCode <= 122) {
e.keyCode -= 32;
}
term.keyDown(e);
this.value = '0';
ctrl = alt = false;
return true;
}
term.keyPress(e);
first = false;
this.value = '0';
return true;
});
}
cols = rows = null;
quit = false;
$ = document.querySelectorAll.bind(document);
send = function(data) {
return ws.send('S' + data);
};
ctl = function() {
var args, params, type;
type = arguments[0], args = 2 <= arguments.length ? __slice.call(arguments, 1) : [];
params = args.join(',');
if (type === 'Resize') {
return ws.send('R' + params);
}
};
ws_url = 'ws://' + document.location.host + '/ws' + location.pathname;
ws = new WebSocket(ws_url);
term = new Terminal($('#wrapper')[0], send, ctl);
ws.onopen = function() {
console.log("WebSocket open", arguments);
return ws.send('R' + term.cols + ',' + term.rows);
};
ws.onerror = function() {
return console.log("WebSocket error", arguments);
};
ws.onmessage = function(e) {
return setTimeout(function() {
return term.write(e.data);
}, 1);
};
ws.onclose = function() {
console.log("WebSocket closed", arguments);
quit = true;
return open('', '_self').close();
};
addEventListener('beforeunload', function() {
if (!quit) {
return 'This will exit the terminal session';
}
});
bench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars in " + ((new Date()).getTime() - t0) + " ms");
};
cbench = function(n) {
var rnd, t0;
if (n == null) {
n = 100000000;
}
rnd = '';
while (rnd.length < n) {
rnd += "\x1b[" + (30 + parseInt(Math.random() * 20)) + "m";
rnd += Math.random().toString(36).substring(2);
}
t0 = (new Date()).getTime();
term.write(rnd);
return console.log("" + n + " chars + colors in " + ((new Date()).getTime() - t0) + " ms");
};

View File

@@ -22,34 +22,31 @@ html, body
padding: 0
line-height: 1.2
body
main
display: flex
height: 100%
flex-direction: column
background-color: black
overflow: hidden
white-space: nowrap
.terminal
flex: 1
outline: none
background-color: $bg
color: $fg
text-shadow: 0 0 $shadow rgba($fg, $shadow-alpha)
transition: 200ms
#wrapper
height: 100%
background-color: black
overflow: hidden
white-space: nowrap
&.bell
-webkit-filter: blur(2px)
.terminal
outline: none
background-color: $bg
color: $fg
text-shadow: 0 0 $shadow rgba($fg, $shadow-alpha)
transition: 200ms
&.skip
-webkit-filter: sepia(1)
&.bell
-webkit-filter: blur(2px)
div
overflow: visible
&.skip
-webkit-filter: sepia(1)
.inline-html
white-space: normal
.line
overflow: visible
.inline-html
white-space: normal
.focus .cursor

File diff suppressed because it is too large Load Diff

View File

@@ -13,7 +13,8 @@
</head>
<body>
<main>
<main id="wrapper">
</main>
<script src="{{ static_url('javascripts/main.js') }}"></script>

5
dev.py
View File

@@ -8,7 +8,10 @@ import shlex
commands = [
'coffee -wcb -j butterfly/static/javascripts/main.js ' +
' '.join(glob('butterfly/static/coffees/*.coffee')),
'butterfly/static/coffees/term.coffee ' +
'butterfly/static/coffees/backsel.coffee ' +
'butterfly/static/coffees/virtual_input.coffee ' +
'butterfly/static/coffees/main.coffee ',
'compass watch butterfly/static',
'python butterfly.server.py ' + ' '.join(sys.argv[1:])
]