blob: 6b0d5cb8ad928f61fe5d94137192c0d0c63b8272 [file] [log] [blame] [view]
[TOC]
# Embedding hterm
This is a quick overview describing how to use hterm in your own application.
Please direct any questions to the [chromium-hterm mailing list].
[chromium-hterm mailing list]: https://groups.google.com/a/chromium.org/forum/?fromgroups#!forum/chromium-hterm
## Get the code
The "libapps" git repository contains hterm and its dependencies.
Clone this repo into a parent directory of your choice.
In this example we'll create `~/src/libapps/`:
```sh
$ mkdir ~/src
$ cd ~/src
$ git clone https://chromium.googlesource.com/apps/libapps
```
## Build hterm
The build process bundles some resources as JavaScript source and concatenates
the JavaScript into a single file (`hterm_all.js`).
```sh
$ cd ~/src/libapps
$ ./hterm/bin/mkdist
```
The files are all written to `./hterm/dist/js/`. Copy the `hterm_all.js` file
into your project.
## Include hterm in your app
Include the generated `hterm_all.js` file in your app in an appropriate manner.
This step should be self evident.
## Initialize hterm
### Terminal node
You'll want a sacrificial DOM node which will become the terminal widget.
It should be a div which is either `position: relative` or `position: absolute`.
In our example, we'll assume this DOM:
```html
<!DOCTYPE html>
<html>
<body>
<div id="terminal"
style="position:relative; width:100%; height:100%"></div>
</body>
</html>
```
### Runtime storage
You'll need to choose a storage implementation.
This is the backing store that hterm will use to read and write preferences.
This should be one of:
* `lib.Storage.Local()` (**default**): If you are a cross-browser web app and
want to use `window.localStorage` (i.e. settings will persist across local
page loads).
* `lib.Storage.Memory()`: If you are a cross-browser web app and want
in-memory storage only (i.e. all settings will be lost on reload).
* `lib.Storage.Chrome(chrome.storage.sync)`: If you are a Chrome extension
and want sync storage (i.e. settings sync between devices).
* `lib.Storage.Chrome(chrome.storage.local)`: If you are a Chrome extension
and want local storage (i.e. settings are per-device).
### Terminal initialization
Create an instance of `hterm.Terminal`:
```js
// profileId is the name of the terminal profile to load, or "default" if
// not specified. If you're using one of the persistent storage
// implementations then this will scope all preferences read/writes to this
// name.
//
// To change the storage settings, pass it as an option here.
// {storage: new lib.Storage.Memory()}
const t = new hterm.Terminal({profileId});
```
Now write an `onTerminalReady` handler.
This will fire once, when the terminal is initialized and ready for use.
```js
t.onTerminalReady = function() {
// Create a new terminal IO object and give it the foreground.
// (The default IO object just prints warning messages about unhandled
// things to the the JS console.)
const io = t.io.push();
io.onVTKeystroke = (str) => {
// Do something useful with str here.
// For example, Secure Shell forwards the string onto the NaCl plugin.
};
io.sendString = (str) => {
// Just like a keystroke, except str was generated by the terminal itself.
// For example, when the user pastes a string.
// Most likely you'll do the same thing as onVTKeystroke.
};
io.onTerminalResize = (columns, rows) => {
// React to size changes here.
// Secure Shell pokes at NaCl, which eventually results in
// some ioctls on the host.
};
// You can call io.push() to foreground a fresh io context, which can
// be uses to give control of the terminal to something else. When that
// thing is complete, should call io.pop() to restore control to the
// previous io object.
};
```
After you've registered your onTerminalReady handler you can connect the
terminal to the sacrificial DOM node.
```js
t.decorate(document.querySelector('#terminal'));
```
### Keyboard setup
After you call the `decorate` helper, you'll usually call `installKeyboard`
to capture all keyboard input when the terminal is focused.
This allows us to react to keyboard shortcuts and such.
```js
t.installKeyboard();
```
## Write to the terminal
Once `onTerminalReady` finishes, you're free to start writing content!
This is the process or connection that wants to display content to the user
(rather than the user sending content).
```js
t.io.print('Print a string without a newline');
t.io.println('Print a string and add CRLF');
```
You usually want to go through the `io` object to display content rather than
going directly through the terminal. It will take care of encoding UTF-16 to
UTF-8 and then sending it via `t.interpret` (which processes escape sequences).
If you use the `t.print` function directly, it won't do either of those things,
which might be what you want.
## Encoding
Modern applications need to make sure they handle data encoding properly at all
times lest we end up with [Mojibake] everywhere.
All data going in & out of hterm is via the `hterm.Terminal.IO` object (which
may be accessed via `t.io` in the code snippets above).
### hterm->app
When hterm passes data to the application (via the `onVTKeystroke` and
`sendString` callbacks), it uses [UTF-16] in native JavaScript strings.
The application should then convert it to whatever encoding it expects.
A common example when working with [WebSockets] is to convert the string to
an [ArrayBuffer] with [UTF-8] encoding.
This is easily accomplished with the [TextEncoder] API.
```js
const encoder = new TextEncoder();
const ws = new WebSocket(...);
...
io.onVTKeystroke = (str) => {
ws.send(encoder.encode(str));
};
```
***note
Note: <=hterm-1.83 offered a `send-encoding` preference where you could select
between `utf-8` and `raw` settings.
The `utf-8` mode would encode [UTF-8] [code unit]s into a JS [UTF-16] string
while the `raw` mode would pass through the [UTF-16] string unmodified.
With >=hterm-1.84, the `send-encoding` preference has been dropped and hterm
always runs in the equivalent `raw` mode.
***
### app->hterm
When passing data to hterm to interpret, strings should be in [UTF-16] encoding.
This covers `hterm.Terminal.IO`'s `print`, `writeUTF16`, `println`, and
`writelnUTF16` APIs as well as `hterm.Terminal`'s `interpret`.
A few APIs are also provided to pass in arrays of [UTF-8] [code unit]s.
This covers `hterm.Terminal.IO`'s `writeUTF8` and `writelnUTF8` APIs.
This can be helpful when streaming binary data from somewhere else.
You should avoid mixing calls to the two sets of functions: either only use
the string-based APIs, or only use the array-based APIs.
This is because [UTF-8] is a multibyte protocol, and if an incomplete [code
point] was written, mixing the APIs could lead to subtle corruption.
If you take care to only write complete [code point]s when using the `writeUTF8`
APIs, then you could call the plain `print` APIs inbetween.
***note
Note: <=hterm-1.90 supported [UTF-16] strings with [UTF-8] [code unit]s.
The API had been deprecated since hterm-1.85, and has been removed with
hterm-1.91+. As a quick hack, `lib.codec.stringToCodeUnitArray` can be
used to convert the string to an array before calling `writeUTF8`.
***
***note
Note: <=hterm-1.84 required `hterm.Terminal.interpret`'s argument to be a string
of [UTF-8] [code unit]s when the `receive-encoding` was set to `utf-8`.
With >=hterm-1.85, `hterm.Terminal.interpret` always uses [UTF-16] strings.
***
[ArrayBuffer]: https://developer.mozilla.org/en-US/docs/Web/API/ArrayBuffer
[code point]: https://en.wikipedia.org/wiki/Character_encoding
[code unit]: https://en.wikipedia.org/wiki/Character_encoding
[Mojibake]: https://en.wikipedia.org/wiki/Mojibake
[TextEncoder]: https://developer.mozilla.org/en-US/docs/Web/API/TextEncoder
[UTF-16]: https://en.wikipedia.org/wiki/UTF-16
[UTF-8]: https://en.wikipedia.org/wiki/UTF-8
[WebSockets]: https://developer.mozilla.org/en-US/docs/Web/API/WebSockets_API