Skip to content
This repository has been archived by the owner on Sep 1, 2023. It is now read-only.

Latest commit

 

History

History
252 lines (191 loc) · 8.12 KB

embed.md

File metadata and controls

252 lines (191 loc) · 8.12 KB

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

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/:

$ 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).

$ 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:

<!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:

// If you are a cross-browser web app and want to use window.localStorage.
hterm.defaultStorage = new lib.Storage.Local();

// If you are a cross-browser web app and want in-memory storage only.
hterm.defaultStorage = new lib.Storage.Memory();

// If you are a Chrome app and want sync storage.
hterm.defaultStorage = new lib.Storage.Chrome(chrome.storage.sync);

// If you are a Chrome app and want local storage.
hterm.defaultStorage = new lib.Storage.Chrome(chrome.storage.local);

Framework initialization

Before using hterm, you'll need to initialize the underlying libdot framework. You do this by calling lib.init() and waiting it for it to finish before calling your own initialization function.

function setupHterm() {
  ... your hterm initialization logic ...
}

// This will be whatever normal entry/initialization point your project uses.
window.onload = async function() {
  await lib.init();
  setupHterm();
};

Terminal initialization

All of the logic below lives within the setupHterm callback.

Create an instance of hterm.Terminal:

// 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.
const t = new hterm.Terminal({profileId});

Now write an onTerminalReady handler. This will fire once, when the terminal is initialized and ready for use.

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.

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.

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).

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.

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 units 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 units. 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 points 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 units. 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 units when the receive-encoding was set to utf-8. With >=hterm-1.85, hterm.Terminal.interpret always uses UTF-16 strings.