When I was choosing what to build (the first post), I said that I wanted a Rust implementation for Wayland. With Rust everything is more or less clear: a fast language, good memory checks, and a compiler that does not let you make too many mistakes. We saw this in the calibration of Jev: almost everything that passed the mechanical checks turned out to be right and did not need to be redone.
Why Wayland only
With Wayland the reader may ask: why this way? Why not go the standard route, choose a good, time-tested cross-platform library and use it?
My answer has two parts. The first one is philosophical. I believe that with LLMs it is possible (or will be in the near future) to build specialised solutions efficiently. Do you want it for macOS or Windows? You create a project like mine, feed it the code of Exerakt, and after some time you have your own version of the application, built and tuned for your environment, in the form you need. The word “soft” in software stops being just an adjective and becomes a literal description of what you do: you change the code to fit the problem, instead of changing the problem to fit the “right” framework.
The second part is technical. A cross-platform library is not only a convenient and free way to get an application that runs everywhere. It is also an extra layer of abstraction that makes you write code not the way you want. It keeps you from using native things the way they were meant to be used. It brings its own complex bugs that often need non-trivial workarounds, and it makes you give up features simply because the framework does not support them yet.
So I chose to specialise for one protocol. And the other decisions went the same way. Wherever I could, I avoided big ready-made solutions. They are good, great, time-tested (no sarcasm), but they would make me bend the code the way the framework needs, not the way I want. I did not want to run, every time, into a feature that “cannot be done the way you want because of this limitation of the framework” and then kick myself, thinking “why did I make this choice? Nobody forced me to use Qt/GTK/your favourite framework”.
Drawing everything myself
The client draws its whole interface by itself, and this gives simple answers to problems that are hard with a toolkit. For example, selecting text across many messages, the problem I had with Fractal: in Exerakt the whole timeline is one text document, so a selection simply goes across messages. HTML is laid out my own way, and themes have no limits beyond what I write.
The stack is small: winit for the window and input, softbuffer to put pixels on the screen,
tiny-skia to draw, and parley to lay out text. Everything is drawn on the CPU first, and the GPU
comes in only if the CPU turns out to be too slow.
These crates are young, so before going further I wrote down the criteria the first milestone had to check. If one of them failed, one crate would be replaced and the others would stay:
- selecting text across several messages and copying it;
- smooth scrolling of a timeline of 2000 messages at 1440p, on the CPU.
The first milestone
The first milestone was a client that can read: log in, restore the session, show the rooms and their history, and send a plain message. It took five steps:
- The client’s model: the data the screen shows, such as rooms and their messages.
- The layer that talks to Matrix: login, the session, and sync through
matrix-sdk. - The window, drawing, and text.
- The views: the room list, the timeline, and the message field.
- Connecting all of it into a working client and testing it end to end against local Matrix servers (Conduit and Synapse).
Selection across messages works. A frame of the timeline with real data at 1440p is drawn in 3 to 4 milliseconds, less than a quarter of the time a frame has at 60 Hz. So the stack stays.
Against Element
Then I compared it with Element Desktop on the same account with 14 rooms, both with a stored session:
| Exerakt | Element Desktop | |
|---|---|---|
| Start to the room list on screen | about 0.25 s | about 2 s |
| Memory after opening a room | 69 MiB | about 700 MiB |
Of course the client is still very far from its final shape, and it does not have a huge number of features that can grow its size and slow its start. Even so, the numbers are very encouraging, and they support my confidence that I should keep going in this direction.
The next post is about the second milestone: reading Matrix messages the way Element shows them.