From 8fd350f0fc17bf48703f2f2093de5c610c956b07 Mon Sep 17 00:00:00 2001 From: Elliot Saba Date: Wed, 5 Oct 2016 07:23:06 +0000 Subject: [PATCH] Add `Terminado` example --- terminado_demo/README.md | 6 ++ terminado_demo/app.py | 16 ++++ terminado_demo/index.html | 89 ++++++++++++++++++ terminado_demo/terminado_attach.js | 142 +++++++++++++++++++++++++++++ 4 files changed, 253 insertions(+) create mode 100644 terminado_demo/README.md create mode 100644 terminado_demo/app.py create mode 100644 terminado_demo/index.html create mode 100644 terminado_demo/terminado_attach.js diff --git a/terminado_demo/README.md b/terminado_demo/README.md new file mode 100644 index 00000000..70e4e18b --- /dev/null +++ b/terminado_demo/README.md @@ -0,0 +1,6 @@ +Using Terminado with Xterm.js +============================= + +Xterm.js is a very flexible system, and as such can be mapped to work with a wide variety of websocket/terminal backends. [Terminado](https://github.com/takluyver/terminado) is one such backend written in Python using `Tornado` as the underlying web framework. This directory shows a more-or-less barebones example of integrating `Terminado` and `Xterm.js`. To do so requires some small edits to `Xterm.js`'s `attach.js` to change the websocket communication format. Briefly, `Terminado` wraps messages in arrays with an element denoting what kind of data it holds, and so rather than simply sending user-typed data directly, one sends `['stdin', data]`. + +To run this example, first install Terminado via `pip install terminado`, then simply run `python ./app.py` and connect to http://localhost:8000 to open a shell on localhost. diff --git a/terminado_demo/app.py b/terminado_demo/app.py new file mode 100644 index 00000000..28fd1cdd --- /dev/null +++ b/terminado_demo/app.py @@ -0,0 +1,16 @@ +"""A single common terminal for all websockets. +""" +import tornado.web +from tornado.ioloop import IOLoop +from terminado import TermSocket, SingleTermManager + +if __name__ == '__main__': + term_manager = SingleTermManager(shell_command=['bash']) + handlers = [ + (r"/websocket", TermSocket, {'term_manager': term_manager}), + (r"/()", tornado.web.StaticFileHandler, {'path':'index.html'}), + (r"/terminado_attach.js()", tornado.web.StaticFileHandler, {'path':'terminado_attach.js'}), + ] + app = tornado.web.Application(handlers, static_path="../dist/") + app.listen(8010) + IOLoop.current().start() diff --git a/terminado_demo/index.html b/terminado_demo/index.html new file mode 100644 index 00000000..b2977ebf --- /dev/null +++ b/terminado_demo/index.html @@ -0,0 +1,89 @@ + + + +Terminado Xterm.js example page + + + + + + + +
+ + diff --git a/terminado_demo/terminado_attach.js b/terminado_demo/terminado_attach.js new file mode 100644 index 00000000..6031c5ad --- /dev/null +++ b/terminado_demo/terminado_attach.js @@ -0,0 +1,142 @@ +/* + * Implements the attach method that + * attaches the terminal to a Terminado WebSocket stream. + * + * The bidirectional argument indicates, whether the terminal should + * send data to the socket as well and is true, by default. + */ + +(function (attach) { + if (typeof exports === 'object' && typeof module === 'object') { + /* + * CommonJS environment + */ + module.exports = attach(require('../../src/xterm')); + } else if (typeof define == 'function') { + /* + * Require.js is available + */ + define(['../../src/xterm'], attach); + } else { + /* + * Plain browser environment + */ + attach(window.Terminal); + } +})(function (Xterm) { + 'use strict'; + + /** + * This module provides methods for attaching a terminal to a WebSocket + * stream. + * + * @module xterm/addons/attach/attach + */ + var exports = {}; + + /** + * Attaches the given terminal to the given socket. + * + * @param {Xterm} term - The terminal to be attached to the given socket. + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + exports.attach = function (term, socket, bidirectional, buffered) { + bidirectional = (typeof bidirectional == 'undefined') ? true : bidirectional; + term.socket = socket; + + term._flushBuffer = function () { + term.write(term._attachSocketBuffer); + term._attachSocketBuffer = null; + clearTimeout(term._attachSocketBufferTimer); + term._attachSocketBufferTimer = null; + }; + + term._pushToBuffer = function (data) { + if (term._attachSocketBuffer) { + term._attachSocketBuffer += data; + } else { + term._attachSocketBuffer = data; + setTimeout(term._flushBuffer, 10); + } + }; + + term._getMessage = function (ev) { + var data = JSON.parse(ev.data) + if( data[0] == "stdout" ) { + if (buffered) { + term._pushToBuffer(data[1]); + } else { + term.write(data[1]); + } + } + }; + + term._sendData = function (data) { + socket.send(JSON.stringify(['stdin', data])); + }; + + term._setSize = function (size) { + socket.send(JSON.stringify(['set_size', size.rows, size.cols])); + }; + + socket.addEventListener('message', term._getMessage); + + if (bidirectional) { + term.on('data', term._sendData); + } + term.on('resize', term._setSize); + + socket.addEventListener('close', term.detach.bind(term, socket)); + socket.addEventListener('error', term.detach.bind(term, socket)); + }; + + /** + * Detaches the given terminal from the given socket + * + * @param {Xterm} term - The terminal to be detached from the given socket. + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + exports.detach = function (term, socket) { + term.off('data', term._sendData); + + socket = (typeof socket == 'undefined') ? term.socket : socket; + + if (socket) { + socket.removeEventListener('message', term._getMessage); + } + + delete term.socket; + }; + + /** + * Attaches the current terminal to the given socket + * + * @param {WebSocket} socket - The socket to attach the current terminal. + * @param {boolean} bidirectional - Whether the terminal should send data + * to the socket as well. + * @param {boolean} buffered - Whether the rendering of incoming data + * should happen instantly or at a maximum + * frequency of 1 rendering per 10ms. + */ + Xterm.prototype.attach = function (socket, bidirectional, buffered) { + return exports.attach(this, socket, bidirectional, buffered); + }; + + /** + * Detaches the current terminal from the given socket. + * + * @param {WebSocket} socket - The socket from which to detach the current + * terminal. + */ + Xterm.prototype.detach = function (socket) { + return exports.detach(this, socket); + }; + + return exports; +});