There’s been a lot of buzz about GUI stuff lately, which made me briefly reflect on the state of the art and share something I’ve been working on as of late.

What's this? Read on, it's a bit of a story...

It seems folks really feel the pain of their ever-expanding tech stacks, multiplied by an explosion of display form factors and related tech such as accessibility. People are coming up with new ways of the GUI (PanGui, Dear ImGui, etc.), which is very exciting and encouraging in itself. Are we getting close yet? You might be led to believe so…

But just a brief glance into the rear-view mirror… in the ’80s and ’90s you could get away with programming Athena (later Motif) in C and proudly (and rightfully) call yourself a GUI programmer. You had a couple thick books on your shelf (X Window System User’s Guide …) which looked intimidating to people visiting you at work (where you had your own room).

In retrospect, those books were nothing compared to today’s tech overload. On the desktop, Motif has been passé forever, so you better moved to Qt or GTK. And suffered through not just their inherent terribleness, but also all the upgrade cycles and framework updates that broke your code. And you better kept your compilers and runtime libraries in lockstep with mutually working versions of everything, and performed magick rituals synced to the phase of the Moon…

Alas, there is a good chance that you also had to work in a different programming language (chiefly C++ or Java or lately Python) instead of just C. Say hello to language bindings, say hello to a new world of bloody complications! Oh, and you might well be targeting something other than those hipster Unix-clones (BSD and Linux), closer to the desktop mainstream… and use something other than the X Window System in the hope of some commercial appeal. So many parallel universes!

Most recently, you might even have had to target mobile devices with their own complete ecosystem of idiosyncrasies, a fresh new field already riddled by proprietary cruft left behind moving fast and breaking everything. Confine yourself into the prison of someone else’s walled garden at no additional cost.

And let’s also consider for a moment the web application, that mythical beast which is such a royal pain to deal with, yet is somehow still hailed as transcending the problems of the “traditional” desktop GUI. Well, it transcends and replaces those with other problems that can only be fixed in the next version of the framework, or if that fails, the next completely new framework. If it hurts, you can always buy a bigger, fatter computer. Or hire yet another Scrum Master. Or just live with it (everything is broken anyway).

If you managed to stay in this industry for 20 years, you know exactly what I’m talking about, and here’s a tip of my hat to you.

Is it getting better?

After all this tremendous effort and churn, you might be right to expect that the state of platforms in general, and chiefly among them the GUI in particular, is getting better. Well, it is certainly getting… somewhere. And folks certainly have their opinions. Various people have decried the decline of usability (continued here); Gnome Files is a concrete case study by the same author. The awesome Tracy Profiler has a complete chapter in its extensive documentation devoted to platform specifics, with subsections such as Apple woes and Android lunacy. And regarding the desktop, the manual has this beautiful passage which I cannot resist to quote in its entirety: “Please don’t ask about window decorations in Gnome. The current behavior is the intended behavior. Gnome does not want windows to have decorations, and Tracy respects that choice. If you find this problematic, use a desktop environment that actually listens to its users.”

Myself being a long-time i3 user (having given up on, and seeking refuge from the madness of Gnome), I still could not shield myself from the damage inflicted by “modern” toolkits: I have fought (and lost) trying to keep a sane, sensibly sized, always-on scrollbar on the side of common everyday apps such as the web browser. As a result, I have come to passionately hate this borderless, relief-less, scrollbar-less trend (clean at the expense of taking away vital visual clues). The term enshittification, coined in the cloud-native world seemingly past the relevance of the traditional desktop, readily springs to mind…

It’s not just end users who are taking a hit. Case in point: most major Linux distributions and even FreeBSD have heavily deprecated GTK2 and programs that rely on it, left and right. The Ardour Digital Audio Workstation has been developed on top of this then-state-of-art toolkit. The Ardour codebase contains who knows how many tens of thousands of lines of code, written to this toolkit. Some two decades later, GTK2 is falling out of favour to such extent that Linux distributions no longer want to provide it to their users. The solution? Embed a subset of GTK2 into the Ardour source tree, and provide binary builds to (professional, paying) users… and of course no plans to upgrade to GTK3 or GTK4, ever. (Because, let’s face it, using GTK2 is still fine if you just wanted a GUI; work on replacing it can never be justified on such a project. It is only this crazy push for deprecations and forced obsolescence that make GTK2 not fine to use.)

I have myself recently spent a full-time week of work reluctantly migrating Aqualung to GTK3, because the alternative is that this otherwise excellently functioning, versatile music player, still loved by many, will soon become unavailable to users. This has been a super painful and frustrating experience – downright traumatic if I consider that it was supposed to be nothing but a dependency update. I cannot even list the many ways migrating the application broke its earlier appearance, and while I managed to fix many things, others are forever lost. It is practically impossible (via CSS or otherwise) to get sane scrollbars; zebra striping list rows (also called “rules hint”) is discouraged and ultimately broken (yeah, I mean it: just try making it work, and you will see that even the frowned-upon but supposedly available means is literally broken). If you have any kind of concrete ideas about how exactly your application should look: welcome to pissing in a hurricane!

Oh, and in case you were wondering: coding-wise, such migrations are far from trivial. These major toolkit versions contain so many breaking changes – and even after fixing your code to compile, they have inevitably introduced some regressions (sometimes subtle, other times crashing) that you have to find and (often painfully) debug for a fix or workaround. By and large, it’s as if you were migrating to a completely different toolkit. Alas, GTK3 is also already declared obsolete, inviting you to this hot mess of migrating further to GTK4… One must wonder: who is the intended audience of such a toolkit? Because certainly no hobbyist open source developer working in their precious free time is going to be able to keep up with such a tsunami of bullshit.

My personal perspective

I was never a GUI programmer per se – most of the time I did GUI stuff merely as part of a larger project or the job that needed doing. Users have this tendency to want to interact with the computer all the time… and someone has to pick up that bill.

I got my first taste of GUI programming with Borland TurboVision and later, C++ Builder. Yeah, that was way before Linux came and finally saved me from the perils of that other OS at the turn of the Millennium. Over the course of many years, I wrote several metric tons of C code creating GTK GUIs; later built a bunch of interfaces in Java Swing and Python TkInter. I created or helped maintain a couple web applications, some of them truly massive and extremely proprietary, with less mainstream technologies… including Common Lisp and Erlang, which opened the door to a new, exciting world: Functional programming! Incremental compilation! Hot code loading! And let’s not forget about that time when I believed C++ was good for GUI applications: to this end I used both WxWidgets and FLTK. (My beliefs of what C++ is good for have since evolved.) I toyed with the world of GPU-accelerated rendering and built a complete OpenGL-based X terminal emulator. I have acquired a non-trivial amount of in-depth knowledge of the ubiquitous X Window System.

I also faced the very numerous and multi-faceted issues of user interface creation. I slogged through endless hours of recompiling and re-launching the application while developing that one small dialog; but you bet my compiled code was crazy fast! Ten years later, that same carefully written code was rotten and no longer compiled (even though it was originally state of the art)! Then, I discovered the need for accessibility and internationalization the hard way (users complaining loudly). For kicks and to make “optimal” use of a work laptop, I cross-compiled my GTK app from Linux to that other OS and made it run there – it was an educational experience, but one I will not voluntarily repeat. I habitually waded through and debugged endless streams of nonsensical warnings coming from bolted-on object systems that should have never existed by virtue of using a better programming language. I could go on with this list for the rest of today. But to be fair, I also had a lot of fun and learned a thing or two about GUIs.

At some point I realized that even though I had many more ideas for programs I wanted to write, I no longer had a desire to actually work on GUI applications. Why? Because it was no longer a fun activity – it became nothing but a constant struggle. Having to write Really Ugly Code to do GUI stuff was one thing; what really got to me was the insane amount of churn, forcing me to touch decades-old code once written and long forgotten (but code that people somehow still expected to use, judging from their complaints). I don’t enjoy looking at my decades old code, now do you?

The line must be drawn somewhere! I never want to rewrite a GTK application to target a new toolkit version, or migrate it to an altogether different toolkit, or have to abandon it due to lack of time and interest in doing such slogging. And oh by the way, the same goes for C++ with its comically frequent updates to the language. Am I supposed to look like a fool in 10 years because “Hey, I just looked at your code, why are you not doing XYZ the right way when everyone knows that is the way to do it?” Well, people seldom like to hear “Because I’ve been doing this before it was cool, and this is how you could do it back then.”

So I guess I need to admit that I’m just too old for facing churn (any churn! zero tolerance!), and adapt my ways accordingly. I have more ideas than what could possibly fit into the rest of my lifetime; I have no time to waste on fixing godforsaken deprecation warnings from nowhere (on a compiler that did not exist when I wrote the code in a version of the language now declared obsolete). That game is for 20 year old code monkeys, and I am no longer one. I want to practice durable computing and I want peace of mind knowing that solving a problem today means it will stay solved (the solution stays runnable and walks the same and talks the same) for the rest of my time. That, or I’m out.

But I’m not going to solve the great GUI crisis or the biggest problems of humanity. I just want to solve my problems. (I’m still releasing my code, just don’t ask me to make it solve your problems. You got the problem, you got the job.)

The road ahead

As with other aspects of my personal computing, I want to design a durable solution that lasts the foreseeable future. This means I need a long-term usable graphical platform and a programming language (and environment) with the same qualities. I am starting a personal experiment – I will write software that is long-term durable with no churn to put up with. I will build the stack on proven foundations and control all critical components on top.

An application of Lindy’s law suggests that the X Window System has another several decades in it, which is all I need. You might not like X as much as I do, but it’s been here and it does the job pretty well. It might go out of fashion (if, say, Wayland gets its act together) but it will always be there. There are quirks for sure, but those are at least stable. I know them. And just in case X becomes so hopelessly outdated sometime that I can really no longer use it, well, I will at least know everything about my graphics stack to be able to port it to whatever else is The Way Of Drawing On And Getting Events From The Screen. And my scrollbars will look the same and work the same. See, I fixed my biggest beef – nobody can take my scrollbars from me, ever!

So yeah, X Window System it is going to be. No GTK, no Wx, Qt, Tk, no nothing, just plain old X. Well, Tk could actually be sort of okay, except for the next thing I have in line: programming language. I don’t want to use (and debug, and eventually get to maintain) some Tk binding, and I sure do not want to spend the rest of my life programming GUIs in Tcl.

So what about the programming language? Well, I’ve done most of my GUI programming in C and C++, and developed a strong opinion about the major toolkits in these languages. Yes, the bolted-on object systems of Gnome on the one hand and the Meta Object Compiler-wraught augmented-C++ madness of Qt-land are both abominations stemming from a simple fact: using an inferior programming language! Inferior to what, you ask? To the ideal applications programming language, of course.

Does such a thing exist? Well, if it does, I don’t think anyone has seen it yet. But let me upset you a little further (I sure hope you are already getting upset!) and take you on a little travel of thought, strolling down an alternative, parallel course of memory lane…

Let’s imagine a world where the Worse is better-philosophy, together with the cut-throat business-first mentality (suits who have no understanding of tech rule this increasingly software-driven world) did not triumph. Let’s pretend that the AI winter did not happen and that the most gifted computer scientists of the era managed to evolve Lisp into the premier computing platform and the high-level programming language (perhaps discounting the embedded space)… Assume that the unfortunate historic imbalance of power between computer science and computing hardware did not result in UNIX and C (and later C++ and Java) becoming dominant… Imagine if, using the mainstream programming language, adding two positive numbers could never result in a negative number!

Crazy talk, I know. But I can’t help but find such thoughts utterly fascinating.

Okay… anyone still with me?

Well, let me do a coming out then: I believe that Common Lisp (henceforth just Lisp, sorry Schemers) is, to this very day, the only industrial strength, mature and stable, standardized, multi-vendor, free and open programming language with the right abstraction capabilities and a tour-de-force of powerful features. Oh, and it incrementally compiles to efficient machine code; performance is rarely a problem (but can be extensively tuned). At the same time, it is dynamic and memory-safe. The world of programming languages is still catching up to what good old Lisp has offered for so many decades.

Still, I try to believe that my relationship with Lisp is a pragmatic one: I am not a fan of Lisp (or anything else); rather, I simply believe writing my own future programs in Lisp is going to be the best long-term investment. All languages have downsides, including Lisp, which is old and crufty. But as a programmer with experience in at least a dozen languages, Lisp still regularly surprises me with how quickly and easily I could actually get something done. No other language has given me this experience; if anything, my estimates prove faulty in the opposite direction.

The birth of a toolkit

So I want to use the X Window System from Common Lisp, both being extremely mature and well-trodden… this must be a solved problem, right? Why, of course it is: there is CLX, “the Common Lisp equivalent to Xlib”. Alas, doing some preliminary tests, I found out that it does not really work. Not if I also want to do modern vector graphics… or GLX and OpenGL… or render anti-aliased modern fonts… sigh. CLX is hopelessly outdated.

Hint: Compare this demo [source] with the GTK via CFFI example!

There are other ways for sure. Consider, for the sake of a thought experiment, something highly repulsive, such as GTK via CFFI. I mean, yes it might work, but this approach has serious issues. First of all, I never ever want to have to do anything with GTK again. There is only pain and suffering in that direction. And even though cl-cffi-gtk does a nice job of wrapping GTK’s bolted-on object system into CLOS, it lets enough GTK API through (in essentially unchanged form) to give me ulcers. Maybe it’s just my PTSD, but no thanks.

On the other end of the Lisp-nativeness spectrum, McCLIM looks and feels, for lack of a better word, weird. CLIM itself is a memento of the Symbolics era, but McCLIM is still actively developed. Yet, it seems to be strangely incomplete. And it all looks alien coming from the familiar world of X. Contrary to what one would expect of such a supposedly mature platform, I have never heard of any significant programs built on CLIM that are relevant today. No mention of OpenGL or any GPU-accelerated drawing either. Most “interesting” parts seem to revolve around some SLIME-y “Lisp IDE” functionality. The closest I came to all the exciting stuff made with this supposedly grandiose toolkit were these screenshots. Then I loaded clim-examples in my SBCL to play around with. Took all of three minutes and a touch of some random slider to end up in the debugger with a SIMPLE-ERROR.

Then there is nodgui (née LTK), seemingly the most promising in the whole bunch. But it tries to side-step the problem instead of owning it, by piping GUI-building commands to a separate process running a Tcl interpreter… and as a corollary, lacks (among other things) a convincing story of supporting OpenGL.

If you are in Shopping Mode (as I once was), you might want to go through this Survey of the State of GUI Programming in Lisp. And just in case, here’s another recap of non-Lisp GUI frameworks. But I won’t spend any further words on all these technologies, because I’ve got stuff to do…

Yes, I am building a new foundation for my future computing, because nothing out there seems to fit my needs – and I am not about to settle for anything less! There is no other way forward than to build my own modern graphics toolkit. By the way, I am not the first person to arrive at such a conclusion. At least it will be more fun debugging my own bugs than fixing GTK deprecations. And I sure get to decide how my scrollbars look. God-mode: check!

I named the toolkit Barium; I like this name because it’s an element. It’s also a pun on chromium; instead of dressing up in chrome this one is going to be bare – to the bone.

Traces of Barium

Programming is understanding, as the common adage goes. So really, the best way to actually understand what makes a toolkit is to sit down and write one. As it turns out, a UI toolkit is, at least conceptually, not very complicated. You open a display connection, perform some initial setup to get elements (top-level windows, widgets, resources etc.) created on the screen or elsewhere, and then enter an endless loop, listening to events from the display and reacting to them, running event handlers and updating state.

The natural way to go about the whole business is a thoroughly object-oriented approach; this is the kind of application where OO really shines. The incoming events can easily be dispatched to the object backing each on-screen widget; the widget classes can decide what to do with each type of event. Binding user-supplied event handlers at run-time is simplified by Lisp’s ability to represent and store first-class functions and later invoke them at will. (That which takes entire swathes of highly suspect ugly code fighting the limitations of the median language is nothing but a hash table of closures in Lisp: cheap and entirely unremarkable.) Multiple inheritance is a good fit to enable specific behaviours (endowing widgets with certain state variables or event handlers), and event dispatch itself is greatly simplified by having multi-methods in the core language (as part of CLOS).

In summary, Common Lisp is a natural fit for hosting a native UI toolkit, which ultimately boils down to a collection of three things: 1. a bunch of widget classes implementing various toolkit elements; 2. an event loop to dispatch events to the objects; and finally 3. supporting libraries that contain CFFI bindings and (shallow) wrappers to low-level system facilities such as Xlib functions. There are several quirks and many hours of trial and error spent making it all work together, but on a high level, the system is not complicated at all.

It also does not have to be huge. How much is Barium? Some 5 kLOC for the toolkit itself; another 5 kLOC of bindings (most of it Xlib, of which quite a few functions are, at this moment, not used at all). 2 kLOC of tests and demos. Not a whole lot of code by any measure!

Barium comes with two labels of conspicuous markings for the discerning owner.

On the front cover:

  • Written in pure Common Lisp for maximum portability. Not tied to a specific CL implementation; tested with both SBCL and CCL to make sure this remains the case.
  • Exclusively targeting the X Window System, directly calling Xlib (via CFFI).
  • Regular drawing is done via the de-facto standard vector graphics library used everywhere: Cairo (also via CFFI). Say hello to anti-aliased, modern fonts!
  • First-class support for OpenGL via GLX (also via CFFI) and cl-opengl.
    • Multiple OpenGL contexts supported in the same application (or window).
    • Seamless co-existence of OpenGL-backed and regular (Cairo-drawn) widgets in the same toplevel window.
  • Can be used anywhere the above platforms and libraries are available. (This includes Linux, *BSD, and who knows what else.) No further dependencies.
  • High performance – calling all the native libraries directly via CFFI translates to very little overhead.

On the back side:

  • A lispy API resembling nodgui (but actually implemented in pure Lisp, using CLOS to encapsulate widgets and other objects; not by talking to a toolkit interpreter).
  • Lispy incremental development without constantly restarting your GUI application.
  • Use a fixed layout or (better) the grid layout manager built into each toplevel window.
  • Containers (with their own grid layouts) are hierarchically embeddable.
  • Optional double-buffered Cairo drawing.
  • Easy to implement new widgets either from scratch or specializing existing ones.
  • Support for X keys and modifiers. Want to bind a handler to Control-Shift-Backspace? No problem.
  • X input method support - use the compose key for entering all those funky characters!

All this is released under the extremely liberal MIT license, because the rope you will hang yourself with wants to be free. The project is now public!

But do not get carried away! Barium is still an experimental prototype lacking several essential widgets and support for many things taken for granted (e.g., advanced, non-toy text support). Such features might get added next week, or never arrive. There is also almost no documentation apart from tests and demos. And the API will be changed as I see fit; at this point Barium is more a GUI research vehicle for myself than a stable platform for others to build on.

Remember: no promises! Do read the full license – I really mean those ALL-CAPS PARTS! Cheers!

A trivial example

If you paid attention to the images liberally sprinkled on this article, you have seen a lot of what Barium is already capable of, here and now. Hint for the curious: all of these and more are available in the Barium Gallery, with direct links to their source code.

To give you a taste of how Barium looks to the programmer, below is a really small demo:


A simple Barium demo application

Here is all the code needed for the above UI:

(defun bmi-calculator ()
  (with-barium ()
    (with-objects ()
        ((win window :title "Calculate your BMI" :width 290 :height 190)
         (entry-h entry :parent win :text "" :x 150 :y 30 :width 100
                        :converter #'entry-convert-number)
         (entry-w entry :parent win :text "" :x 150 :y 70 :width 100
                        :converter #'entry-convert-number)
         (calc button :parent win :x 30 :y 120 :padding 8 :text "Calculate")
         (label-bmi label :parent win :x 150 :y 126 :text "" :padding 2
                          :font (make-font :weight :bold))
         (lh label :parent win :x 20 :y 32 :width 120 :text "Height [cm]:"
                   :x-align :right)
         (lw label :parent win :x 20 :y 72 :width 120 :text "Weight [kg]:"
                   :x-align :right))

      (with-signal calc :clicked
        (let ((h (/ (get-value entry-h) 100))
              (w (get-value entry-w)))
          (set-text label-bmi (format nil "BMI: ~$" (/ w h h)))))

      (with-registered-key win '(:control :q) key
        (close-window win))

      (show win)
      (event-loop))))

If it looks like a lot of code, it is because we are using a fixed layout and thus have the burden of specifying meaningful pixel coordinates and dimensions to make the layout work. Using the grid layout manager simplifies this, but I did not want to overwhelm you just yet.

As you can see, there is built-in protection from inappropriate input. This is part of Barium auto-converting the entered text to whatever is convenient for our program – in this case, numbers of any form (integer, float, or even rational). Here we just plugged in a ready-made converter, but could have supplied our own to validate and normalize the input to whatever made sense.

You might have noticed the dashed border around the button, indicating keyboard focus. This is also a feature Barium readily provides: TAB and Shift-TAB cycle through the entries and the button for convenient keyboard-only operation (focus order defaults to widget creation order, but can be customized). There is also support for binding window-global key combinations to event handlers invoked regardless of the focused widget. In the example above, we always let the user quit with Control-Q.

Epilogue

In music, I find that sometimes the saddest songs are the most beautiful. In our world of utter insanitysoftware engineering, sometimes the most exciting things are born out of sheer anger and frustration. I just wanted a goddamn scrollbar. Well, I got my scrollbar with a complete widget toolkit, and working OpenGL gears surrounded by my very own buttons controlling them, and the rest of the whole fucking jungle. But I did get my scrollbar, and I am now kind of happy because I know I can get pretty much anything else I want, and nobody can deprecate it or take it away from me. And I think I deserve it, because I worked very hard for this scrollbar.

At this point I do not expect anyone else to use Barium, and if they do, I certainly hope they understand the tradeoffs they are making. What I wanted to show in this article is that there is always a less traveled road worth pursuing, or at least considering, given enough courage and conviction.

Stay tuned for more substantial applications implemented on top of Barium. You see, I got programs to write; Barium is just a vehicle to make writing them worthwhile.

Until next time!