In my last article I introduced Barium, the X toolkit natively written in Common Lisp. After releasing it this April, I moved on to other, yet to be published projects, and then to having a great summer. I finally came back to Barium in late August. From then up to now, I spent about ten weeks of mostly full-time work bringing the toolkit one notch further. The result is the second release of Barium, bringing lots of cool new features. Here’s a summary.

Let’s start with the big game. Barium now provides menu bars (to be used at the top of windows as a main application menu) to open up potentially hierarchically embedded (cascading) menus:

The same kind of menu can be popped up on demand (via right-click or whatever mechanism preferred by the application):

Menus are built from a simple recursive descriptor (list structure) supplied by the user application. To understand how to use this feature, take a look at the example source.

Menus provide support for:

  • seamless navigation with the mouse and arrow keys
  • hotkeys (auto-registered as window-global hotkeys; displayed in Emacs notation)
  • shortcut keys (underscored with red; flashed on menubar while pressing and holding Alt)
  • icons
  • tick-boxes
  • radio selectors
  • separators

Panes!

Panes are helpful when arranging complex user interfaces. They help the user feel in control as they allow seamless resizing of their subdivisions.

A pane can be split horizontally (a hpane) or vertically (a vpane), and can contain one, two, or even more child widgets:

This little test program (example source) presents multiple horizontal panes stacked in a vertical pane. The image above is a composite showing the cursor over all pane dividers.

A notebook!

Similar to a pane, a notebook can be helpful when arranging complex user interfaces with many elements. Contrary to a pane, a notebook shows only one of its children at any moment.

The notebook allows runtime management (inserting and removing) of its children that make up the pages, and also supports custom widgets placed into the selector tabs. The above example (source) demonstrates this with tabs that contain a label and an image.

Dialogs!

Barium now provides a small (but hopefully growing) set of dialogs ready to be invoked by the application. They are nothing magical, they are just like any application code you would implement yourself. (Example source)

Message dialog

The message dialog contains a textview and a button to close the window. It can be invoked by providing the text (and optionally any other arguments) of the textview as well as (optionally) the title, the caption of the close button, and text area dimensions (width and height). If any of the latter are provided, the text will be scrollable in the respective direction; if omitted, the window will assume the size required by the textview.

File chooser dialog

This dialog lets the user browse the filesystem (starting from an arbitrary directory) going up to the parent or down into any subdirectory; to apply filters specified by the application; to toggle the visibility of hidden files; and to select a file (unless hidden by the chosen filter).

Double-clicking on list entries (or pressing Enter while they are selected) invokes Select, which concludes the dialog or enters the selected subdirectory.

When invoking the dialog with :freeform-entry t an additional entry for entering an arbitrary filename is included. A variant of an existing file can quickly be specified by clicking on it in the list (populating its name into the entry), then editing it. A new subdirectory named after the entry text can also be created by pressing + Folder. This is suitable for using the dialog for picking the destination of “Save As…” operations.

A flexible event loop!

Real-world applications need support for managing and reacting to events other than just those originating from the X Window System itself. Generally, such events indicate a piece of input data becoming available to the application, having arrived over some communications channel (e.g., a TCP socket). Another common case is when an application needs to perform an action in a timed manner, either in a recurring fashion or by deferring it for some time into the future. Reaching such a deadline is another kind of event. Finally, applications might have a need for running code “in the background” or as an “idle task”, as frequently as possible, without compromising the application’s responsiveness to external events.

To support all these use cases, the main event loop (tasked with processing and dispatching X Window System events) is now a first-class object in Barium. It is made available for applications to manage (add or remove) their own handlers to various events on demand. This capability supports event-driven (async) programming, allowing the creation of highly efficient yet comprehensible programs without the need to run concurrent activities in separate threads (and the added complexity associated with synchronizing and communicating between them).

Handlers can be registered for:

  • file descriptor read and error events
  • periodic (recurring) timer events
  • deferred (run-once) timer events
  • idle events (to be run as frequently as possible)

An example (source) demonstrating timed events, both periodic and deferred:

A separate demo, the chatroom, demonstrates I/O multiplexing on file descriptors (network sockets) in the Barium event loop. Running all code in a single thread avoids the many traps and complexities of coordinating between threads and makes the program much easier to reason about. It is also the best design for high performance applications!

shell

With Barium’s newly added ba-ext:shell facility, it is possible to create an inferior process to run an external program concurrently with Common Lisp. The standard input, output and error of this process is connected to file descriptors accessible from Lisp. These descriptors can be used to plug user-defined handlers into the Barium event loop, allowing you to build a fully event-driven (async) application where GUI events and I/O are handled by the same thread. To perform actual I/O from Lisp, use make-fd-stream (hopefully supplied by your Common Lisp implementation) to obtain a stream.

This example program (source) runs /bin/sh and allows you to interact with it. Note that this is not meant to be a functional terminal application, just a small demo!

N.B.: Barium provides its own shell facility because existing solutions such as uiop:launch-program do not offer access to the underlying file descriptors of the I/O streams between Lisp and the external program. Since Barium’s event loop is based on the POSIX poll() facility, a file descriptor is required for each input stream.

Other improvements

The below list covers much of what is left, but is still not exhaustive – some fixes were omitted for brevity.

Grid: fixed aspect ratio of child widgets

The grid (Barium’s layout manager) is now able to accommodate aspect ratio constraints (width per height) as specified on its children, as the below example demonstrates (source):

When using this feature, it is up to the application programmer to set up the grid in a sensible way. For example, the grid ought to have some rubber-band (non-constrained) area in both directions that can freely take up any extra space. Also, the initial subdivision of the grid should comply with the requested aspect ratios. This can be achieved either by appropriately set base size on the widgets’ part, or manual setting of grid row/col min-size constraints.

In the general case, it is clearly impossible to satisfy all constraints. The constraint solver works on a best-effort basis and concludes in a limited maximum number of steps even if the achieved fit remains sub-optimal. In practice, the fewer the number of aspect-constrained widgets, the better results you can expect.

Double-clicks!

There is no double-click on the X event level. Double-clicks are a purely synthetic construct, based on a temporal pattern of multiple button press and release events. Barium now recognizes such a pattern as a double-click, given that the interval between the first button release and the second button press is less than *double-click-threshold-seconds*, that the button is the same in both cases, and that the pointer did not move during this interval.

The button-press-event and button-release-event both have an additional field named double, carrying a boolean value. This is nil for all cases except for the press and release of a double-click, which carry t.

Thus, normal handling of button events need not concern double-clicks at all; the double flag on these events can be diregarded. In other words, both the first and the second click of the double-click will be seen as ordinary button press and release events (just as before), and there is no separate event for the double-click. In case a double-click handler is desired, simply set up a button-press-event handler, and look at the double flag. If that is t, react to the double-click.

Scrollbars!

Yeah, my favourite widgets ever. They received a little polish to make them look better. (Can you spot the difference?) More importantly, I added a timed repeat of the scroll-step action when the mouse button is kept pressed down on one of the arrows. This works just like typematic repeat: a longer initial delay (2/3 seconds) followed by a shorter repeat delay (1/10 seconds) and is built on the newly added event loop features.

Altered X focus model

The X focus model of Barium-created toplevel windows has been altered from Globally Active to Locally Active.

Due to this change, Barium windows will no longer get raised automatically when they receive pointer focus (the mouse pointer is moved over them). Rather, they behave like most other application windows: they receive focus when the pointer enters them, but a click is required to raise them (bring them forward). Of course, exact behaviour is up to the policies of your window manager.

Another consequence is that after a Barium application quits and all its windows are destroyed, the focus is properly moved to whatever application window is underneath. Previously the focus was lost (set to None) and one had to explicitly focus another window. Again, this is more in line with how most other application windows behave.

Performance improvements

The biggest improvement is due to caching (memoization) of calls to cairo:text-extents. The native call into Cairo proved to be a CPU-hog especially when repeatedly reflowing lengthy text in a textview being resized by the user (generating thousands of calls over the same set of strings with the same font). The cache is periodically cleared behind the scenes so you should not need to worry about it.

Shout-out to cl-flamegraph which made it an absolute breeze to identify hotspots!

Breaking changes

  • The refresher class has been removed; the per-frame refresh callback must be implemented using Barium’s event loop facilities. Take a look at the Gears and Shader VAO examples, or the molview demo for inspiration.

  • Widget class key event handlers (that is, on-event methods specialized on key-press-event and key-release-event) are now expected to return a boolean indicating whether the event was acted upon. If the return value is nil, the kbd-arbiter will then dispatch the key event, potentially invoking globally bound key handlers. This allows a widget to selectively override a globally bound key handler, carving out local use of an otherwise occupied key when the widget has focus. It is important that a widget’s key event handler returns t only if the actual key event was significant to the widget. This way, any other key events (not interesting to the widget) are still let through to activate globally bound handlers, e.g., menu hotkeys.

Conclusion

Ten weeks is a long time! I can barely remember how Barium looked like before I started on this batch of work, because so much of it is new. I really enjoyed working on these widgets and capabilities – but now I look forward to something else…

As hinted before, I am (still) working on some actual applications using Barium, and plan to publish one of them pretty soon. Apart from being (hopefully) interesting in its own right, it will also provide more insight into how to use and build on top of Barium in the context of a larger desktop application.

For now, though, you need to be satisfied with the demo apps and small test programs that come with Barium itself. They are all conveniently linked to from the Barium Gallery.

Visit the main project page of Barium for instructions on obtaining and installing the toolkit. If you play with it or use it in any capacity, I would love to hear from you!