Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions terminado_demo/README.md
Original file line number Diff line number Diff line change
@@ -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.
16 changes: 16 additions & 0 deletions terminado_demo/app.py
Original file line number Diff line number Diff line change
@@ -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()
89 changes: 89 additions & 0 deletions terminado_demo/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>Terminado Xterm.js example page</title>
<style>
html {
background: #555;
height: 100vh;
}

body {
min-height: 100vh;
}

#terminal-container {
position: fixed;
top: 0;
bottom: 0;
left: 0;
right: 0;
width: auto;
height: auto;
z-index: 255;
}
</style>
<link rel="stylesheet" href="/static/xterm.css"/>
<script src="/static/xterm.js"></script>
<script src="/terminado_attach.js"></script>
<script>
var term,
charWidth,
charHeight;

// This chunk of code gratefully stolen/adapted from fit.js
function calculate_character_dimensions() {
subjectRow = term.rowContainer.firstElementChild;
contentBuffer = subjectRow.innerHTML;

subjectRow.style.display = 'inline';
subjectRow.innerHTML = 'W';
charWidth = subjectRow.getBoundingClientRect().width;
subjectRow.style.display = '';
charHeight = parseInt(subjectRow.offsetHeight);
subjectRow.innerHTML = contentBuffer;
}

function createTerminal(websocket_url) {
var terminalContainer = document.getElementById('terminal-container');

// Clean terminal
while (terminalContainer.children.length) {
terminalContainer.removeChild(terminalContainer.children[0]);
}

term = new Terminal({
cursorBlink: true
});

protocol = (location.protocol === 'https:') ? 'wss://' : 'ws://';
socketURL = protocol + location.hostname + ((location.port) ? (':' + location.port) : '') + websocket_url;

term.open(terminalContainer);
var socket = new WebSocket(socketURL);
socket.onopen = function() {
term.attach(socket);
term._initialized = true;

terminalContainer.style.width = '100vw';
terminalContainer.style.height = '100vh';
calculate_character_dimensions();
window.onresize = function() {
cols = Math.floor(terminalContainer.getBoundingClientRect().width/charWidth);
rows = Math.floor(terminalContainer.getBoundingClientRect().height/charHeight);
term.resize(cols, rows);
}
window.onresize()
}
}

window.onload = function() {
// Connect to Terminado, listening on /websocket
createTerminal('/websocket')
}
</script>
</head>
<body>
<div id="terminal-container"></div>
</pre>
</body>
142 changes: 142 additions & 0 deletions terminado_demo/terminado_attach.js
Original file line number Diff line number Diff line change
@@ -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;
Copy link
Member

@Tyriar Tyriar Oct 5, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@parisk related to my comments on the PR, we should come up with some namespacing convention to prevent variable collisions with other addons. Something like:

// _ prefix for private, followed by addon name
term._terminado = {};
term._terminado.socket = socket;
term._terminado._flushBuffer = function () {
...

// addon name prefix
Xterm.prototype.terminadoAttach = ...


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;
});