| [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 |