Myxine: a slithery sea-friend to help you get GUI fast

woodcut sketch of myxine glutinosa, the hagfish

Hagfish, a.k.a. myxine glutinosa, are an eel-like sea creatures best known for their ability to make a lot of slime.

By analogy, myxine quickly reduces the friction in creating a dynamic graphical interface, helping you to get GUI fast in any language under the sea.

TL;DR:

If you write a function in any programming language that makes some HTML, Myxine can give you a dynamic webpage whose content instantly reflects whatever you'd like it to show. You can then listen to events within that page to quickly prototype a reactive user interface—with only a knowledge of HTML and your favorite language.

Q: Could you show me something cool, then tell me the details after?
A: Happily, let's get started!

Q: I want to know all about how it works, then can you show me the demo after?
A: Sure thing, let's dig in!

Show me!

First, install myxine, get a cup of tea while it builds, and then come back here :)

Second, make sure you have Python 3 and the requests library installed. Myxine doesn't itself depend on them, but we'll need them presently because this example happens to be written in Python. If you have Python 3 (and therefore hopefully pip3) on your system, you can install requests with:

bash $ pip3 install requests

Now, in one terminal window, run:

bash $ cargo run --release

And in another window, run:

bash $ ./examples/circles.py

Then open up http://localhost:1123/ in your web browser, and mouse around! See if you can figure out what's going on by reading the Python source for this example, or read on for the full story...

Getting started

Installation

You will need a recent version of the Rust programming langauge and its build tool, cargo. If you don't have that, here's the quick-start for installing Rust. Once you have cargo installed, run:

bash $ cargo build --release

We're not yet on crates.io but will be soon! Once we are, you'll be able to install with cargo install myxine.

Running

Myxine is meant to run in the background. It might live longer than any individual program that uses it, and it's meant to be a service many programs might use at the same time. To get started, run:

bash $ cargo run --release

Let's play!

Myxine speaks to the world through HTTP requests and responses. If you can make a web request to localhost from your program, you can use myxine.

Open your browser to http://localhost:1123/, then watch what happens when you run this command in your terminal:

bash $ curl 'localhost:1123/' \ -d '<h1 style="color: blue; padding: 20pt; font-family: Helvetica"> Splish splash! </h1>'

What's going on:

  1. If you POST some HTML to localhost:1123/some/arbitrary/path, and then
  2. GET (i.e. navigate with your web browser) from localhost:1123/some/arbitrary/path: you'll see a web page with the HTML fragment you just posted set as the contents of its <body>.
  3. When you POST some more HTML to that same path, the changes will be instantly updated on the web page before your eyes!

Some more things you can do:

Interactivity

Interfaces are meant to be interactive: myxine lets you listen to events happening in the page without writing a lick of JavaScript.

Listening to the event stream

To listen to the events happening in a page, send a GET request to that page's URL, with the query string ?events. Using curl, this might look like below (some data has been elided for brevity).

```bash $ curl 'localhost:1123/some/path?events' id: [{"attributes":{},"tagName":"html"}] event: mousemove data: {"x":352,"y":237, ... }

id: [] event: blur data: {}

:

id: [] event: focus data: {}

id: [{"attributes":{"style":"margin: 0px; padding: 0px"},"tagName":"body"},{"attributes":{},"tagName":"html"}] event: keydown data: {"altKey":false,"ctrlKey":false,"key":"f","metaKey":false,"shiftKey":false, ... } ```

This will return an endless stream of events in the text/event-stream format, a line-based text format for streams of events with attached data.

Understanding events

In myxine's case, every event will have:

  1. id: A JSON list of "target" objects identifying the path from the most specific location of the event in the document to the least specific. Each target object in this path has a tagName string identifying the HTML tag of the corresponding element, and an attributes dictionary giving the value of each attribute of that element. The list of targets may be empty, in which case it corresponds to an event that fired directly on some top-level object in the browser (as is the case for the blur and focus events in the above example).
  2. event: The name of the JavaScript event to which this item corresponds.
  3. data: A JSON dictionary holding the properties of the event. Different events have different sets of properties associated with them, so the contents of this dictionary may vary depending on the event you are examining.

So, for instance, you click on an element <div id="something", class="cool"></div>, the corresponding event will look something like:

id: [{"tagName":"div","attributes":{"id":"something","class":"cool"}}, ... ] event: click data: {"x":352,"y":237, ... }

If your language doesn't implement a parser for this format, check out the 17-line Python implementation as a reference. For the technical details I used when writing this parser, see the W3C Recommendation for Server-Sent Events and look at the sections for parsing and interpretation. You can ignore everything about what to do "as a user-agent" because you are not a user-agent :)

Example: For an example of an interactive page using event subscriptions, check out the circles example in Python. Make sure myxine is running, then run:

bash $ ./examples/circles.py

Then load up http://localhost:1123/ and mouse around!

Supported events

There are many kinds of user-interface events which can happen in the browser. Most of them are supported by myxine, but not all. Those which aren't usually are one or more of:

The master list of supported events is programmatically defined in the JSON file enabled-events.json, and the hierarchy of events and their interfaces can be visualized in this clickable graphic. This file defines a subset of the standardized DOM events in JavaScript, as well as the inheritance hierarchy for the interfaces of those events and the fields which are to be reported for each event interface. This list is intentionally conservative: if you are in need of support for another event or set of events, feel free to submit a PR with changes to this file.

The escape hatch: evaluating arbitrary JavaScript

It occasionally might become necessary for you to directly evaluate some JavaScript within the context of the page. The most frequent reason for this is to query the value of some object, such as the current contents of a text-box, or the current window dimensions. To allow this, myxine exposes a simple API to send arbitrary JavaScript to the page and return its result: the ?evaluate query string.

There are two ways to use this API, corresponding to JavaScript's notions of "expression" and "statement". The more convenient of the two evaluates a given string as an expression and returns its value:

bash $ curl -X POST "http://localhost:1123/?evaluate=window.innerWidth" 1224

This form is succinct: you don't have to use JavaScript's return keyword, and you can specify everything in the URL itself. However, you can't evaluate multiple lines delimited by semicolons (since the input is interpreted as an expression), and you must percent-escape all special characters like spaces.

To circumvent these limitations, myxine also provides a "statement" form of the ?evaluate API, where the POST body is used as a multi-line block of statements to be evaluated:

bash $ curl "http://localhost:1123/?evaluate" -d \ 'let x = 100; let y = 200; return x + y;' 300

In this form, return is mandatory to send back a value, but there is no need to escape special characters, and multiple statements can be executed as a block.

Further details