Move to xterm.js for CJK and IME support (#22)

This commit is contained in:
Shuanglei Tao
2016-11-09 20:02:04 +08:00
committed by GitHub
parent 24c3f2a880
commit e0e6d89870
14 changed files with 1802 additions and 700 deletions

2
.gitattributes vendored
View File

@@ -1 +1 @@
index.html linguist-vendored
src/index.html linguist-vendored

View File

@@ -1,23 +1,23 @@
# ttyd - Share your terminal over the web [![Build Status](https://travis-ci.org/tsl0922/ttyd.svg?branch=master)](https://travis-ci.org/tsl0922/ttyd)
ttyd is a simple command-line tool for sharing terminal over the web, inspired by [GoTTY](https://github.com/yudai/gotty).
ttyd is a simple command-line tool for sharing terminal over the web, inspired by [GoTTY][1].
![screenshot](screenshot.gif)
# Features
- Build on [libwebsockets](https://libwebsockets.org) with C for speed
- Full terminal emulation based on [hterm](https://chromium.googlesource.com/apps/libapps/+/HEAD/hterm)
- SSL support based on [OpenSSL](https://www.openssl.org)
- Built on top of [Libwebsockets][2] with C for speed
- Full terminal emulation based on [Xterm.js][3] with CJK and IME support
- SSL support based on [OpenSSL][4]
- Run any custom command with options
- Basic authentication support
- Cross platform: macOS, Linux, [OpenWrt](https://openwrt.org)/[LEDE](https://www.lede-project.org)
- Basic authentication support and many other custom options
- Cross platform: macOS, Linux, [OpenWrt][5]/[LEDE][6]
# Installation
## Install on macOS
Install with [homebrew](http://brew.sh):
Install with [homebrew][7]:
```bash
brew install ttyd
@@ -35,7 +35,7 @@ cmake ..
make && make install
```
> **NOTE:** You may need to compile libwebsockets from source for ubuntu versions old than 16.04, since they have outdated `libwebsockets-dev` package ([Issue #6](https://github.com/tsl0922/ttyd/issues/6)).
> **NOTE:** You may need to compile libwebsockets from source for ubuntu versions old than 16.04, since they have outdated `libwebsockets-dev` package ([Issue #6][9]).
## Install on OpenWrt/LEDE
@@ -43,7 +43,7 @@ make && make install
opkg install ttyd
```
> **NOTE:** This may only works for [LEDE](https://www.lede-project.org) snapshots currently, if the install command fails, compile it yourself.
> **NOTE:** This may only works for [LEDE][6] snapshots currently, if the install command fails, compile it yourself.
# Usage
@@ -57,7 +57,7 @@ VERSION:
1.1.0
OPTIONS:
--port, -p Port to listen (default: 7681)
--port, -p Port to listen (default: 7681, use `0` for random port)
--interface, -i Network interface to bind
--credential, -c Credential for Basic Authentication (format: username:password)
--uid, -u User id to run with
@@ -88,5 +88,16 @@ Then open <http://localhost:7681>, now you can see and control the `bash` consol
# Credits
- [GoTTY](https://github.com/yudai/gotty): ttyd is a port of GoTTY to `C` language.
- [hterm](https://chromium.googlesource.com/apps/libapps/+/HEAD/hterm): ttyd uses hterm to run a terminal emulator on the web.
- [GoTTY][1]: ttyd is a port of GoTTY to `C` language.
- [Libwebsockets][2]: used to build the websocket server.
- [Xterm.js][3]: used to run the terminal emulator on the web, former: [hterm][8].
[1]: https://github.com/yudai/gotty
[2]: https://libwebsockets.org
[3]: https://github.com/sourcelair/xterm.js
[4]: https://www.openssl.org
[5]: https://openwrt.org
[6]: https://www.lede-project.org
[7]: http://brew.sh
[8]: https://chromium.googlesource.com/apps/libapps/+/HEAD/hterm
[9]: https://github.com/tsl0922/ttyd/issues/6

47
html/.gitignore vendored Normal file
View File

@@ -0,0 +1,47 @@
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity

10
html/README.md Normal file
View File

@@ -0,0 +1,10 @@
### Build the inlined html
install [Yarn](https://yarnpkg.com), then run the following commands:
```bash
yarn
yarn run build
```
this will compile the inlined html to `../src/index.html`.

45
html/css/app.css Normal file
View File

@@ -0,0 +1,45 @@
body, #terminal-container {
position: absolute;
height: 100%;
width: 100%;
margin: 0px;
font-size:15px;
font-variant-ligatures: none;
-webkit-font-smoothing: antialiased;
background-color: #101010;
}
.terminal {
background-color: #101010;
color: #f0f0f0;
font-family: "DejaVu Sans Mono","Everson Mono",FreeMono,Menlo,Terminal,monospace;
border: 0;
height: 100%;
width: 100%;
}
.terminal .xterm-viewport {
background-color: #101010;
overflow-x: hidden;
}
.terminal .terminal-cursor {
background-color: #f0f0f0;
color: #101010;
opacity: .7;
}
.terminal:not(.focus) .terminal-cursor {
outline: 1px solid #f0f0f0;
}
@keyframes blink-cursor {
0% {
background-color: #f0f0f0;
color: #101010;
}
50% {
background-color: transparent;
color: #f0f0f0;
}
}

BIN
html/favicon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

10
html/gulpfile.js Normal file
View File

@@ -0,0 +1,10 @@
var gulp = require('gulp'),
inlinesource = require('gulp-inline-source');
gulp.task('inlinesource', function () {
return gulp.src('*.html')
.pipe(inlinesource())
.pipe(gulp.dest('../src'));
});
gulp.task('default', ['inlinesource']);

19
html/index.html Normal file
View File

@@ -0,0 +1,19 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
<title>ttyd - Terminal</title>
<link inline rel="icon" type="image/png" href="favicon.png">
<link inline href="node_modules/xterm/dist/xterm.css">
<link inline href="css/app.css">
<script inline src="node_modules/xterm/dist/xterm.js"></script>
<script inline src="node_modules/xterm/addons/fit/fit.js"></script>
<script inline src="js/overlay/overlay.js"></script>
</head>
<body>
<div id="terminal-container"></div>
<script src="auth_token.js"></script>
<script inline src="js/app.js"></script>
</body>
</html>

109
html/js/app.js Normal file
View File

@@ -0,0 +1,109 @@
(function() {
var terminalContainer = document.getElementById('terminal-container'),
httpsEnabled = window.location.protocol == "https:",
url = (httpsEnabled ? 'wss://' : 'ws://') + window.location.host + window.location.pathname + 'ws',
protocols = ["tty"],
autoReconnect = -1,
term, pingTimer;
var openWs = function() {
var ws = new WebSocket(url, protocols);
ws.onopen = function(event) {
if (typeof tty_auth_token !== 'undefined') {
ws.send(JSON.stringify({AuthToken: tty_auth_token}));
}
pingTimer = setInterval(sendPing, 30 * 1000, ws);
if (typeof term !== 'undefined') {
term.destroy();
}
term = new Terminal();
term.on('resize', function (size) {
if (ws.readyState === WebSocket.OPEN) {
ws.send("2" + JSON.stringify({columns: size.cols, rows: size.rows}));
}
setTimeout(function() {
term.showOverlay(size.cols + 'x' + size.rows);
}, 500);
});
term.on("data", function(data) {
if (ws.readyState === WebSocket.OPEN) {
ws.send("0" + data);
}
});
window.onresize = function(event) {
term.fit();
};
while (terminalContainer.firstChild) {
terminalContainer.removeChild(terminalContainer.firstChild);
}
term.open(terminalContainer);
term.fit();
term.focus();
};
ws.onmessage = function(event) {
var data = event.data.slice(1);
switch(event.data[0]) {
case '0':
term.write(decodeURIComponent(escape(window.atob(data))));
break;
case '1': // pong
break;
case '2':
document.title = data;
break;
case '3':
var preferences = JSON.parse(data);
Object.keys(preferences).forEach(function(key) {
console.log("Setting " + key + ": " + preferences[key]);
term.setOption(key, preferences[key]);
});
break;
case '4':
autoReconnect = JSON.parse(data);
console.log("Enabling reconnect: " + autoReconnect + " seconds")
break;
}
};
ws.onclose = function(event) {
if (term) {
term.off('data');
term.off('resize');
term.showOverlay("Connection Closed", null);
}
clearInterval(pingTimer);
if (autoReconnect > 0) {
setTimeout(openWs, autoReconnect * 1000);
}
};
ws.onerror = function(event) {
var errorNode = document.createElement('div');
errorNode.style.cssText = [
"color: red",
"background-color: white",
"font-size: x-large",
"opacity: 0.75",
"text-align: center",
"margin: 1em",
"padding: 0.2em",
"border: 0.1em dotted #ccc"
].join(";");
errorNode.textContent = "Websocket handshake failed!";
terminalContainer.insertBefore(errorNode, terminalContainer.firstChild);
};
};
var sendPing = function(ws) {
ws.send("1");
};
openWs();
})()

View File

@@ -0,0 +1,56 @@
// ported from hterm.Terminal.prototype.showOverlay
Terminal.prototype.showOverlay = function(msg, timeout) {
if (!this.overlayNode_) {
if (!this.element)
return;
this.overlayNode_ = document.createElement('div');
this.overlayNode_.style.cssText = (
'border-radius: 15px;' +
'font-size: xx-large;' +
'opacity: 0.75;' +
'padding: 0.2em 0.5em 0.2em 0.5em;' +
'position: absolute;' +
'-webkit-user-select: none;' +
'-webkit-transition: opacity 180ms ease-in;' +
'-moz-user-select: none;' +
'-moz-transition: opacity 180ms ease-in;');
this.overlayNode_.addEventListener('mousedown', function(e) {
e.preventDefault();
e.stopPropagation();
}, true);
}
this.overlayNode_.style.color = "#101010";
this.overlayNode_.style.backgroundColor = "#f0f0f0";
this.overlayNode_.textContent = msg;
this.overlayNode_.style.opacity = '0.75';
if (!this.overlayNode_.parentNode)
this.element.appendChild(this.overlayNode_);
var divSize = this.element.getBoundingClientRect();
var overlaySize = this.overlayNode_.getBoundingClientRect();
this.overlayNode_.style.top =
(divSize.height - overlaySize.height) / 2 + 'px';
this.overlayNode_.style.left = (divSize.width - overlaySize.width) / 2 + 'px';
var self = this;
if (this.overlayTimeout_)
clearTimeout(this.overlayTimeout_);
if (timeout === null)
return;
this.overlayTimeout_ = setTimeout(function() {
self.overlayNode_.style.opacity = '0';
self.overlayTimeout_ = setTimeout(function() {
if (self.overlayNode_.parentNode)
self.overlayNode_.parentNode.removeChild(self.overlayNode_);
self.overlayTimeout_ = null;
self.overlayNode_.style.opacity = '0.75';
}, 200);
}, timeout || 1500);
};

20
html/package.json Normal file
View File

@@ -0,0 +1,20 @@
{
"name": "ttyd",
"version": "1.1.0",
"description": "Share your terminal over the web",
"main": "js/app.js",
"repository": {
"url": "git@github.com:tsl0922/ttyd.git",
"type": "git"
},
"author": "Shuanglei Tao <tsl0922@gmail.com>",
"license": "MIT",
"scripts": {
"build": "gulp"
},
"dependencies": {
"gulp": "^3.9.1",
"gulp-inline-source": "^3.0.0",
"xterm": "^2.1.0"
}
}

1448
html/yarn.lock Normal file

File diff suppressed because it is too large Load Diff

695
src/index.html vendored

File diff suppressed because one or more lines are too long

View File

@@ -49,12 +49,12 @@ parse_window_size(const char *json) {
struct json_object *o = NULL;
if (!json_object_object_get_ex(obj, "columns", &o)) {
lwsl_err("columns field not exists!");
lwsl_err("columns field not exists!\n");
return NULL;
}
columns = json_object_get_int(o);
if (!json_object_object_get_ex(obj, "rows", &o)) {
lwsl_err("rows field not exists!");
lwsl_err("rows field not exists!\n");
return NULL;
}
rows = json_object_get_int(o);