Post

X11:11 Make a Fish

Try out the live version, or check out the source on GitHub.

Intro

“Serverless X11 client” is a term that should strike fear into any self respecting IT practitioner. Marrying X11 (legacy, hardware coupled, long lasting, realtime) with serverless (cloud native, ephemeral, abstract) makes no goddamn sense. This is exactly why I had to do it

Demo video

Solve for X11

X11 is the windowing system that UNIX, and later Linux, grew up on. Instead of having to manually draw each and every pixel, a program can make X11 calls to draw a window, render text, and get input events. Although it’s slowly being supplanted by the upstart Wayland, it’s remarkable for surviving since the mid 1980s essentially unchanged. Owing to its heritage in the era of the minicomputer, X11 is network transparent, meaning the application and terminal can be on separate machines, and communicate over TCP.

X11 swaps the usual server/client nomenclature. The box the user sits in front of is referred to as a server, and the program running on a large computer far away is known as the client. I suspect this is a holdover from the unique design of MIT’s internal network, but I have no evidence besides a friend who worked IT there thinking “that sounds like something they’d do”

Nearly all X11 client/server pairs are on the same machine, or forwarded over SSH to appear like they’re on the same machine, but most servers still support just accepting TCP packets unencrypted and unauthenticated. God bless legacy support.

Webfishing

Creating fish-based, time-gated sites has become a bit of a tradition within my friend group. To quote fish influencer Brooke Chalmers:

My friend adryd introduced me to the original makea.fish site (HTTP only) made by weepingwitch. The site embeds an image rendered with PHP showing either a randomly-generated fish or the phrase “come back at 11:11.” Getting the 11:11 fish has become somewhat of a ritual among my friends (we have a Discord channel specifically for posting fish screenshots!)

Over the last few months, we’ve made a few fun variations on this:

It has gotten to a point where we even have multiple trackers for all the different sites. I was lying in bed with a friend coming up with project ideas when I said the phrase “X11:11 Make a Fish”. I’m a firm believer that projects with good names are destined for greatness, so I started work on a program to display a drawing of a fish in an X11 window immediately.

Implementation

X11 applications are almost always written in C, however my 9-5 is as a C developer, so I try not to use it for personal projects. Luckily, there are a few implementations of X11 in Rust. I am a subscriber to the school of programming by hacking up example code, and thankfully there was an example to display a bitmap image in the archaic PPM format. Imagemagick produced 1bpp images that X11 didn’t like, but that was fixed with find and replace to fix the palette. I then had an executable that displayed either a fish, or a message to come back at 11:11. As with many information technology ventures, deployment turned out the be the bigger issue. I didn’t relish the idea of having my personal server flinging out X11 packets, and I find spinning up VPSs to be a pain. Luckily, it’s 2024 and we have agile infrastructure for our shitposts. A screenshot displaying multiple overlapping windows on a computer screen. The top window features a colorful, animated fish against a blue background labeled ‘makea.fish.’ Below it, an SSH session window titled ‘fissh.breq.dev’ shows ASCII art of a fish with the current time displayed as ‘11:11:09 pm’ and the text ‘make a fish.’ To the right, a window labeled ‘X11:11 makeafish’ shows an outline drawing of a detailed fish. A small clock interface labeled ‘xclock’ is visible in the bottom left corner, showing an analog clock face. A browser with a tree-style tab list is partially visible on the left. XFish running (swimming?) alongside other fish

λ

Serverless is a cloud-native architecture where user code is run directly by providers such as AWS. The advantage is you don’t have to think about servers: implementation details such as operating system, dependencies, and networking are abstracted away. It’s well suited for workloads that can be broken up into independent parts, or are small and don’t require much state. Although the code is run in its own little world and usually only communicates via HTTPS, under the hood it’s running in a full Linux environment and can do more complicated things like file interactions and making arbitrary network connections. X11 uses an unusual topology where the application connects to an open socket on the terminal, which coincidentally works perfectly for ephemeral services like this. Amazon’s Lambda also has first class Rust support, so I was able to hardcode my residential IP address into my Rust source, use Cargo to upload it, and open port 6000 to my laptop as a proof of concept test. I assumed there would be some error related to an X11 library or making random outbound TCP connections, but to my complete shock, when I clicked test in the AWS console, a window popped up on my laptop initiated by an Amazon.com server hundreds of miles away. A screenshot of the AWS Lambda console showing a test event configuration screen. The test event action is set to ‘Create new event,’ with the event name entered as ‘test_event.’ The ‘Event sharing settings’ are set to ‘Private.’ Below, there is a JSON template named ‘hello-world.’ Overlaid on the screenshot is a window titled ‘X11:11 makeafish,’ displaying a black-and-white sketch of a fish labeled ‘Lacalala alcalina.’ Fish Testing

Putting it together

While in the past I would’ve had to setup an API Gateway to be able to run my program from anything besides the AWS console, function URLs provide an easy way to invoke Lambda functions. They also parse URL parameters which allowed me to pass in the IP address - however for some reason it is handled completely differently than the parameters passed in by the debug interface/API gateway. This difference was both poorly documented and annoying to debug. I wasn’t sure what the best UI for passing the server IP address; I experimented with a telnet interface or just passing in URL parameters manually. I ended up writing a super simple HTML/JS page I could host on my website to redirect the user with the proper URL options. This page also performs the time verification to only display the fish at 11:11 in the users timezone. Now it had an interface, I was able to post it on Discord for folks to try out. It was super exciting to see it on a variety of systems, although the combination of having to open ports to the internet and reconfiguring their X server to accept unauthenticaed X11 connections lead to some (completely justified) reticence.

Fish generation

Despite being completely arbitrary and quite silly, the project felt like something was missing. I was surprised how essential the procedural generation is to the Joy of Fish; displaying the same photo every time is Boring, so I set out to do something more interesting. I considered using fish from art scans, or code from another fish project, but I also wanted to maintain the look of Xaw with its 1 BPP graphics. On a lark, I typed “procedurally generate a line drawing of a fish” into Google and to my shock I found a library that…procedurally generates line drawings of fish. Grabbing other parts of the X11RB examples, I modified the program to parse the CSV output of the library into a list of polylines, and draw those using X11 calls from yet more examples. I was even able to add a delay to the event loop to give it a drawing effect. The one downside is that fishdraw is written in Javascript, which most cloud native developers will tell you is not Rust. If this were running on a traditional server, I would’ve probably run a subcommand call to node, but that’s not possible in lambda-land. After a few false starts running the Javascript in Deno (which failed because the two async implemetnations were incpmatible in a way I don’t quite understand), and running the Javascript clientside (couldn’t figure out how to get the redirect working with a POST and browsers do NOT like passing 200kb URL params), I zeroed in on having another microservice to generate the fish using a Javascript based Lambda. Once again, if I were using an API Gateway I could’ve done some kind of RPC call, but I just hardcoded the URL into a Reqwest call; this is Not The Way To Do It but it works well. Although none of this is planned, the dataflow ended up being this: The diagram represents a workflow where a local X11 server communicates through a TCP connection. An HTML XFish site hosted on GitHub Pages sends GET requests, which are processed by a Rust XFish application running on AWS Lambda. This Rust application can interact with another AWS Lambda function written in JavaScript to generate a CSV of points representing fish. The CSV data is then sent back to the Rust application, completing the interaction loop.’ M I C R O S E R V I C E S This is my favorite diagram I’ve ever created

This post is licensed under CC BY 4.0 by the author.