An addon that adds a PTY layer to xterm.js

Overview

xterm-pty

This is an addon that adds a PTY layer to xterm.js. It is useful to run an Emscripten'ed CUI program.

See the demo site: https://xterm-pty.netlify.app/.

How to use

If you want to use this library to run a CUI program built with Emscripten, you can go to Section "Emscripten integration".

Install xterm-pty as usual.

$ npm i xterm-pty

Use LineDisciplineAddon.write and LineDisciplineAddon.onData instead of Terminal.write and Terminal.onData of xterm.js.

{ xterm.write(`Hi, ${ slave.read().trim() }!\n`); });">
// Start an xterm.js instance
const xterm = new Terminal();

// Create master/slave objects
const { master, slave } = openpty();

// Connect the master object to xterm.js
xterm.loadAddon(ldiscAddon);

// Use slave.write instead of xterm.write
slave.write("Hello, world!\nInput your name:");

// Use slave.onReadable and slave.read instead of xterm.onData
slave.onReadable(() => {
  xterm.write(`Hi, ${ slave.read().trim() }!\n`);
});

Result:

Hello, world!

Input your name: Yusuke
Hi, Yusuke!
■

Emscripten integration

Assume you want to run example.c in xterm.js.

  1. Compile it with Emscripten. Note that you need to specify -sNO_EXIT_RUNTIME=1 -sFORCE_FILESYSTEM=1.
emcc -sNO_EXIT_RUNTIME=1 -sFORCE_FILESYSTEM=1 -o example-core.js example.c

This will generate two files, example-core.js and example-core.wasm.

  1. Write a Web Worker script to run example-core.js as follows.
{ importScripts(location.origin + "/example-core.js"); emscriptenHack(new TtyClient(msg.data)); };">
// example.worker.js
importScripts("https://cdn.jsdelivr.net/npm/[email protected]/workerTools.js");

onmessage = (msg) => {
  importScripts(location.origin + "/example-core.js");

  emscriptenHack(new TtyClient(msg.data));
};

The function emscriptenHack intercepts some syscall events like TTY read/write, ioctl, and select in the Emscripten runtime. The helper class TtyClient sends TTY requests to the server that works in the main thread (UI thread).

  1. Write a HTML as follows.
">
<html>
  <head>
    <title>demo</title>
    <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/[email protected]/css/xterm.css">
  </head>
  <body>
    <div id="terminal"></div>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/lib/xterm.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/[email protected]/index.js"></script>
    <script>
      const xterm = new Terminal();
      xterm.open(document.getElementById("terminal"));

      const { master, slave } = openpty();
      xterm.loadAddon(master);

      const worker = new Worker("./example.worker.js");
      new TtyServer(slave).start(worker);
    </script>
  </body>
</html>

It sets up xterm.js, and invokes example.worker.js as a Web Worker. The helper class TtyServer accepts TTY request from the client that works in the Web Worker.

  1. Serve all four files, example.html, example.worker.js, example-core.js, and example-core.wasm, with the following custom headers, and access the server with Google Chrome or Firefox.
Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

These headers are needed to enable SharedArrayBuffer. Note that you need to use Google Chrome or Firefox.

Here is a minimal server script in Ruby:

require "webrick"

class Server < WEBrick::HTTPServer
  def service(req, res)
    super
    res["Cross-Origin-Opener-Policy"] = "same-origin"
    res["Cross-Origin-Embedder-Policy"] = "require-corp"
  end
end

Server.new(Port: 8080, DocumentRoot: ".").start

Caveats

Currently, emscriptenHack rewrites the Emscripten runtime to monitor the following system events.

  • eead from the file descripter 0
  • write to the file descripters 1 and 2
  • ioctl for TCGETS (getting termios), TCSETS and families (setting termios), and TIOCGWINSZ (gettins window size)
  • select for TTY (waiting for stdin to be readable)

The hack highly depends on the internal implementation of the Emscripten runtime. We confirmed that it worked on Emscripten 2.0.13, which is bundled in Ubuntu 21.10.

Also, to use xterm-pty to run an Emscripten'ed process, you need to run it in a Web Worker. This is because the Emscripten runtime is written as synchronous Javascript code. When an Emscripten'ed process attempts to read its stdin, the runtime invokes a callback to receive input. The callback function is not async, but need to wait synchronously for input from xterm.js. To achieve this, we need to use SharedArrayBuffer and Atomics.wait. This is why the response header described above is needed.

Unfortunately, xterm-pty does not support dynamic change of window size; you cannot use xterm-pty with xterm-addon-fit. We need to send SIGWINCH signal to tell an Emscripten'ed process when the terminal is resized, but Emscripten does not support signals yet.

Component flow

From xterm.js to the Emscripten runtime:

graph TB
    subgraph Main thread
        A(xterm.js) -->|Terminal.onData| B[PTY Master]
        subgraph "PTY"
          B -->|LineDiscipline.writeFromLower| C[LineDiscipline]
          C -->|LineDiscipline.onWriteToUpper| D[PTY Slave]
        end
        D -->|Slave.read| E[TtyServer]
    end
    subgraph Web Worker thread
        E -->|SharedArrayBuffer| F[TtyClient]
        F -->|TtyClient.onRead| G[emscriptenHack]
        G -->|dirty hack| H(Emscripten runtime)
    end

From the Emscripten runtime to xterm.js:

graph BT
    subgraph Main thread
        B[PTY Master] -->|Terminal.write| A(xterm.js)
        subgraph "PTY"
          C[LineDiscipline] -->|LineDiscipline.onWriteToLower| B
          D[PTY Slave] -->|LineDiscipline.writeFromUpper| C
        end
        E[TtyServer] -->|Slave.write| D
    end
    subgraph Web Worker thread
        F[TtyClient] -->|Worker.postMessage| E
        G[emscriptenHack] -->|TtyClient.onWrite| F
        H(Emscripten runtime) -->|dirty hack| G
    end
You might also like...

p5.grain is a p5.js addon for conveniently adding grain and texture overlays to artworks.

🌾 p5.grain p5.grain is a p5.js addon for conveniently adding grain and texture overlays to artworks. p5.grain was also created with fxhash projects i

Dec 24, 2022

Storybook Addon Root Attributes to switch html, body or some element attributes (multiple) at runtime for you story

Storybook Addon Root Attributes to switch html, body or some element attributes (multiple) at runtime for you story

Storybook Addon Root Attributes What is this This project was inspired by le0pard/storybook-addon-root-attribute The existing library received only on

Sep 6, 2022

An addon/plugin template for Zotero.

Zotero Addon Template This is an addon/plugin template for Zotero. Documentation(Chinese, provides English translation) 👍 You are currently in bootst

Jan 2, 2023

Ember.js addon allowing you to easily implement non-CRUD actions for your Ember Data models

@mainmatter/ember-api-actions This is an Ember.js addon allowing you to easily implement non-CRUD actions for your Ember Data models. Compatibility Em

Dec 15, 2022

Lightweight analytics abstraction layer for tracking page views, custom events, & identifying visitors

 Lightweight analytics abstraction layer for tracking page views, custom events, & identifying visitors

A lightweight analytics abstraction library for tracking page views, custom events, & identify visitors. Designed to work with any third-party analyti

Dec 31, 2022

:flashlight: Set a spotlight focus on DOM element adding a overlay layer to the rest of the page

:flashlight: Set a spotlight focus on DOM element adding a overlay layer to the rest of the page

Focusable An awesome and lightweight library for performing spotlight in your DOM elements, setting an animated overlay to the rest of the page. You c

Dec 7, 2022

The missing Javascript smart persistent layer

Basil.js The missing Javascript smart persistence layer. Unified localstorage, cookie and session storage JavaScript API. Philosophy Basil aims to eas

Dec 2, 2022

Some ideas for modern multi-layer page transitions using CSS Animations.

Multi-Layer Page Reveal Effects Some ideas for modern multi-layer page transitions using CSS Animations. Article on Codrops Demo Sponsor Demo sponsore

Nov 1, 2022

a hack to allow direct connections to unifi protect on a different layer 3 network

unifi-proxy Very rudimentary tools to interface with Unifi devices over the UDP discovery protocol (port 10001). This was originally written to allow

Nov 11, 2022

The Web 3.0 social layer built on top of Twitter

The Web 3.0 social layer built on top of Twitter

Niftycase – The Web 3.0 Chrome extension for Twitter Niftycase is a open-source Chrome extension that allows you to view anybody's NFTs directly on Tw

Jul 14, 2022

(IDW) Interpolated Heatmap Layer for mapbox-gl

(IDW) Interpolated Heatmap Layer for mapbox-gl

Mapbox :: Interpolated Heatmap(s) InterpolateHeatmapLayer is a minimalist JavaScript library for rendering temperature maps (or interpolate heatmaps)

Dec 15, 2022

Solid.js library adding a services layer for global shared state.

Solid.js library adding a services layer for global shared state.

Solid Services Services are "global" objects useful for features that require shared state or persistent connections. Example uses of services might i

Dec 30, 2022

Absolutely minimal view layer for building web interfaces.

Superfine Superfine is a minimal view layer for building web interfaces. Think Hyperapp without the framework—no state machines, effects, or subscript

Dec 29, 2022

DOM ViewModel - A thin, fast, dependency-free vdom view layer

DOM ViewModel - A thin, fast, dependency-free vdom view layer

domvm (DOM ViewModel) A thin, fast, dependency-free vdom view layer (MIT Licensed) Introduction domvm is a flexible, pure-js view layer for building h

Dec 8, 2022

A platform designed specifically as an additional layer on top of Google Classroom for students to gain the best out of online evaluations

A platform designed specifically as an additional layer on top of Google Classroom for students to gain the best out of online evaluations

Peer-Learning-Platform A platform designed specifically as an additional layer on top of Google Classroom for students to gain the best out of online

Jun 12, 2022

Defines the communication layer between mobile native(iOS/Android) and webview using JSON Schema and automatically generates SDK code

Defines the communication layer between mobile native(iOS/Android) and webview using JSON Schema and automatically generates SDK code.

Dec 8, 2022

BI, API and Automation layer for your Engineering Operations data

BI, API and Automation layer for your Engineering Operations data

Faros Community Edition Faros Community Edition (CE) is an open-source engineering operations platform that connects the dots between all your operati

Dec 23, 2022

🪐 The IPFS gateway for NFT.Storage is not "another gateway", but a caching layer for NFTs that sits on top of existing IPFS public gateways.

nftstorage.link The IPFS gateway for nft.storage is not "another gateway", but a caching layer for NFT’s that sits on top of existing IPFS public gate

Dec 19, 2022

🍰 An extensible, layer based shader material for ThreeJS

🍰 An extensible, layer based shader material for ThreeJS

lamina 🍰 An extensible, layer based shader material for ThreeJS These demos are real, you can click them! They contain the full code, too. 📦 More ex

Jan 6, 2023
Comments
  • Running with a more recent emscripten?

    Running with a more recent emscripten?

    I was trying to use your library to build an emscripten version of gap (github.com/gap-system/gap). It seems to set up mostly well, but input isn't being handled correctly (a newline seems to appear after every character, and full lines don't seem to get sent correctly).

    Before I start deeply debugging, I noticed I'm using emscripten 3.1.23, but you mention using the internals of a specific, earlier, version. I need to use a reasonable recent emscripten, as I need some fixes relating to ASYNCIFY functionality. Would you expect the library to work with 3.1.23, or where should I look to see how to make it work?

    At present building your own emscripten GAP is very fiddly, I'd be happy to provide it if you want, but I'd probably want to tidy it up a bit first.

    opened by ChrisJefferson 8
  • Why is there an extra `-1` attached to the end of input?

    Why is there an extra `-1` attached to the end of input?

    I was toying around with this project, and I noticed that sometimes, the input data gathered from xterm/pty side, seems to contain an extra -1.

    Upon some investigation I managed to narrow down to this line, so the question is: why appending an extra -1 here? Is this due to some historic conventions?

    Many thanks!

    opened by xxuejie 4
  • Make the read syscall work correctly

    Make the read syscall work correctly

    When the input buffer is empty, the read syscall must return if it has already read at least one byte, and must wait for more input if it has not yet read any byte.

    Emscripten's read syscall calls stream.tty.ops.get_char repeatedly until the specified length of bytes is read or get_char returns null.

    https://github.com/emscripten-core/emscripten/blob/353fffeef2e6dfaefcd03f6d9c93866b610bd929/src/library_tty.js#L58-L81

    Our emscriptenHack function overwrites the get_char function. So, when the input buffer is empty, it should wait for input if it is the first call to get_char in the current read syscall, and return null if it is the second or subsequent call.

    However, get_char did not know whether it is the first call or not. This changeset intecepts TTY.stream_ops.read to set the flag so that get_char can determine if it is called the first time.

    Fixes #3

    opened by mame 1
  • Move IO flush hack from ttyServer to emscriptenHack

    Move IO flush hack from ttyServer to emscriptenHack

    Previously, ttyServer.onRead returned -1 which meant IO flush or EOF. This is for the behavior of get_char of Emscripten internal. However, this hack is Emscripten-specific, so it would be good to handle it in emscriptenHack instead of ttyServer.

    Fixes #1

    opened by mame 1
Owner
Yusuke Endoh
World No.1 IOCCC player. http://www.ioccc.org/winners.html#Yusuke_Endoh
Yusuke Endoh
A discord bot which adds command for searching things in hoogle.

dalmatian-the-bot Prerequisite You should set following environment variables to run the app. HOOGLE = Server address where hoogle service is running

하스켈 학교 4 Nov 20, 2022
Image addon for xterm.js

xterm-addon-image Image output in xterm.js. ⚠️ This is an experimental addon, that is still under construction. ⚠️ Install from npm (not yet released)

null 28 Dec 11, 2022
ZxCDDoS for education with LAYER 7, LAYER 4, AMP METHODS

?? ZxCDDoS: Release v1.0 - Free DDoS Panel ?? Terminal only accepts ANSI color. Username: admin Password: admin Language Logs Fixed L7 methods (crash,

zxcr9999 151 Jan 3, 2023
An Ember CLI Addon that provides a variety of UI components.

We use https://waffle.io/softlayer/sl-ember-components to work our issues. What sl-ember-components is An Ember CLI Addon that provides UI components

SoftLayer 115 May 7, 2022
Ember-cli addon for using Bootstrap as native Ember components.

ember-bootstrap An ember-cli addon for using Bootstrap 4 und 5 in Ember applications. The addon includes the Bootstrap CSS (or Sass, Less) in your pro

kaliber5 491 Dec 25, 2022
Simple & Quick Access Addon For Home Assistant

Home Assistant - Firefox Addon Quick Access Home Assistant - Firefox Addon Usage Create a Custom Dashboard With Quick Access Entity In Home Assistant

Varun Sridharan 7 Dec 25, 2022
Simple & Quick Access Addon For Homer Dashboard

Homer Dashboard - Firefox Addon Quick Access Homer Dashboard - Firefox Addon Usage Install Addon [ Firefox ] Configure The Addon Add The Quick Access

Varun Sridharan 7 Jan 22, 2022
L'addon qui sauveras les prochaines années

GachaCleaner Une Extension Firefox pour purifier la lecture Download Changer "iel" ?? en : Permet de changer l'inexistant terme en quelque chose qui e

null 7 Jul 4, 2022
Jump to github urls (browser addon)

Currently a Firefox addon. Find GitHub locations quickly using your browser's history. Usage Use your mouse or keyboard. Use the filter to search for

madprops 4 Nov 28, 2022
This is a dependency-free easy-to-use vanilla JavaScript addon allowing you to create HTML currency inputs with various different currencies and formattings.

intl-currency-input This is a dependency-free easy-to-use vanilla JavaScript addon allowing you to create HTML currency inputs with various different

null 6 Jan 4, 2023