npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

xterm-pty

v0.10.2

Published

A pty for xterm.js and Emscripten

Downloads

612

Readme

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.

// 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(master);

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

Reading user input (e.g. via functions like fgets) requires pausing the WebAssembly app.

We can't block the main thread as that will prevent any events, including user input, from waking the application and causing the deadlock. Instead, we support two modes of asynchronous pausing via corresponding Emscripten features.

PThread proxying

You can compile your application with -pthread -s PROXY_TO_PTHREAD. In this mode Emscripten will transparently move your application to run in a pthread (in a Web Worker).

xterm-pty will use proxying to read from and write to the Xterm.js terminal on the main thread and pause the "main" pthread until results are received.

In this mode, any written content will be flushed to the screen as soon as possible, regardless of whether the proxied pthread is blocked or not, which makes it particularly useful for running applications that write content non-stop, such as the Sloane demo.

However, if your application needs direct access to DOM via Embind or custom JavaScript, you'll need more work to proxy those operations yourself as Web Workers don't have direct access to the DOM.

Also, PThreads rely on SharedArrayBuffer and atomics, which is a relatively new feature and might be not available in older browsers. It also requires that you serve your application with the cross-origin isolation headers:

Cross-Origin-Opener-Policy: same-origin
Cross-Origin-Embedder-Policy: require-corp

See this explainer for more details on these headers.

Asyncify

If you want your application running on the main thread, you can instead compile it with -s ASYNCIFY.

In this mode Emscripten will rewrite the WebAssembly application, making it behave as one large async-await function. This allows asynchronous pausing right on the main thread without actually blocking it.

One downside is that it currently adds a noticeable size overhead to the resulting WebAssembly binary.

Another is that, for performance reasons, we'll only automatically pause to read user input (e.g. via fgets) and not to flush any output, so if your application writes a lot of output non-stop like the earlier mentioned Sloane demo, you won't see it appear on the screen until you manually pause the application with e.g. emscripten_sleep(0).

Example

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

  1. Compile it with Emscripten with either -s ASYNCIFY or -s PROXY_TO_PTHREAD.

    Include xterm-pty's Emscripten integration library via --js-library=[path to xterm-pty]/emscripten-pty.js. We'll use ES6 module output as that's the easiest way to pass some options to the generated Emscripten Module, but feel free to use any other output format.

    emcc -s ASYNCIFY --js-library=node_modules/xterm-pty/emscripten-pty.js -o example.mjs example.c

    This will generate two files, example.mjs and example.wasm.

  2. Write a HTML as follows.

<!DOCTYPE html>
<html>
  <head>
    <link rel="stylesheet" href="https://unpkg.com/[email protected]/css/xterm.css" />
  </head>
  <body>
    <div id="terminal"></div>
    <script type="module">
      import 'https://unpkg.com/[email protected]/lib/xterm.js';
      import 'https://unpkg.com/xterm-pty/index.js';
      import initEmscripten from './example.mjs';

      var xterm = new Terminal();
      xterm.open(document.getElementById('terminal'));

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

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

      await initEmscripten({
        pty: slave
      });
    </script>
  </body>
</html>

Caveats

Currently, Emscripten integration library patches the Emscripten runtime functions to intercept TTY access.

  • fd_read to pause on reads from the file descriptor 0
  • TTY write to stdout and stderr to redirect the output to the terminal
  • ioctl_* for TCGETS (getting termios), TCSETS and families (setting termios), and TIOCGWINSZ (gettins window size)
  • poll and newselect syscalls to pause while waiting for stdin to become readable

It also sends terminal signals to the Emscripten'd application so that Ctrl+C for termination or terminal resizing should work as expected.

The integration hack highly depends on the internal implementation of the Emscripten runtime. It's confirmed to work with Emscripten 3.1.47, which can be installed via emsdk install 3.1.47.

Typical data flow

From xterm.js to the Emscripten runtime:

graph TB
  A(xterm.js) -->|Terminal.onData| B[PTY Master]
  subgraph "PTY"
    B -->|LineDiscipline.writeFromLower| C[LineDiscipline]
    C -->|LineDiscipline.onWriteToUpper| D[PTY Slave]
  end
  subgraph "emcc-generated code"
    D -->|Slave.read| E[emscripten-pty.js]
    E -->|overridden read syscall| F[Emscripten runtime]
  end

From the Emscripten runtime to xterm.js:

graph BT
  B[PTY Master] -->|Terminal.write| A(xterm.js)
  subgraph "PTY"
    C[LineDiscipline] -->|LineDiscipline.onWriteToLower| B
    D[PTY Slave] -->|LineDiscipline.writeFromUpper| C
  end
  subgraph "emcc-generated code"
    E[emscripten-pty.js] -->|Slave.write| D
    F[Emscripten runtime] -->|overridden write syscall| E
  end

How to build

To build xterm-pty, run:

npm install && npm run build

To build the demo, run:

cd demo && npm install && npm run build

To preview the demo after editing xterm-pty, the following command is useful.

cd `git rev-parse --show-toplevel` && npm run build && cd demo && rm -rf node_modules/ && npm install && npm run dev