GNOME 45/46 Retrospective

My creative work is more aligned to GNOME cycles than years. Now that GNOME 46 is is out it’s a good time to look back at some of the larger things I did during those cycles.

Fedora and Frame Pointers

2023 kicked off with quite a kerfuffle around frame pointers.

Many people appear to have opinions on the topic though very few are aware of the trade-offs involved or the surface area of the problem domain. I spent quite some time writing articles to both educate and ultimately convince the Fedora council that enabling them is the single best thing they could do to help us make the operating system significantly faster release-to-release.

Much to my surprise both Ubuntu and Arch are choosing to follow.

Early this year I published an article in Fedora Magazine on the topic.

Futures, Fibers and Await for C

I still write a lot of C and have to integrate with a lot of C in my day to day work. Though I really miss asynchronous programming from other languages like when I was working on Mono all those years. Doing that sort of programming in C with the GObject stack was always painful due to the whole async/finish flow.

For decades we had other ways in C but none of them integrated well with GObject and come with their own sort of foot-guns.

So I put together libdex which could do futures/promises, fibers (including on threads), lock-free work-stealing among thread-pools, io_uring integration, asynchronous semaphores, channels, and more.

It’s really changed how I write C now especially with asychronous workflows. Being able to await on any number of futures which suspend your fiber is so handy. It reminds me a lot of the CCR library out of the Microsoft Robotic Labs way back when. I especially love that I can set up complex “if-these-or-that” style futures and await on them.

I think the part I’m most proud of is the global work queue for the thread-pool. Combining eventfd with EFD_SEMAPHRE and using io_uring worked extremely well and doesn’t suffer the thundering herd problem that you’d get if you did that with poll() or even epoll(). Being able to have work-stealing and a single worker wake up as something enters the global queue was not something I could do 20 years ago on Linux.

Where this advanced even further is that it was combined with a GMainContext on the worker threads. That too was not something that worked well in the past meaning that if you used threads you had to often forgo any sort of scheduled/repeated work.

Sysprof

Now that I was armed with actually functioning stack traces coming from the Linux perf subsystem it was time to beef up Sysprof.

Pretty much everything but the capture format was rewritten. Primarily because the way containers are constructed on Linux broke every assumption that profilers written 10 years ago had. Plus this was an opportunity to put libdex through its paces and it turned out great.

Correlating marks across GTK, GLib, and Mutter was extremely valuable.

A screenshot showing timer markings from various parts of the platform.

Though the most valuable part is probably the addition of flamegraphs.

A screenshot of a flamegraph showing profiler data for Text Editor

Search Providers

To test out the Sysprof rewrite I took a couple weeks to find and fix performance issues before the GNOME release. That uncovered a lot of unexpected performance issues in search providers. Fixes got in, CPU cycles were salvaged. Projects like Photos, Calculator, Characters, Nautilus, and libgweather are all performing well now it seems. I no longer find myself disabling search providers on new GNOME installations.

This work also caught JXL wallpapers adding double-digit seconds to our login time. That got pushed back a release while upstream improved GdkPixbuf loader performance.

GNOME Settings Daemon

Another little hidden memory stealer was gnome-settings-daemon because of all the isolated sub-daemons it starts. Each of these were parsing the default theme at startup which is fallout from the Meson build system handover. I had fixed this many years ago and now it works again to both save a small amount (maybe 1-3mb each) of memory for each process and reduced start-up time.

libmks

At one point I wanted to see what it would take to get Boxes on GTK 4. It seemed like one of the major impediments was a GTK 4 library to display as well as handle input (mouse, keyboard). That was something I worked on considerably in the past during my tenure at a large virtualization company so the problem domain is one I felt like I could contribute to.

I put together a prototype which led me to some interesting findings along the way.

Notably, both Qemu and the Linux kernel virtio_gpu drivers were not passing damage regions which prevented GTK from doing damage efficiently. I pointed out the offending code to both projects and now those are fixed. Now you can use the drm backend in a Qemu VM with VirGL and virtio_gpu and have minimal damage all the way through the host.

That work then got picked up by Bilal and Benjamin which has resulted in new APIs inside of GTK and consumed from libmks to further optimize damage regions.

Qemu however may still need to break it’s D-Bus API to properly pass DMA-BUF modifiers.

GtkExpression Robustness

While working on Sysprof I ran into a number of issues with GtkExpression and GObject weak-ref safety. When you do as much threading as Sysprof does you’re bound to break things. Thankfully I had to deal with similar issues in Builder years ago so I took that knowledge to fix GtkExpression.

By combining both a GWeakRef and a GWeakNotify you can more safely track tertiary object disposal without races.

GObject Type-System Performance

Also out of the Sysprof work came a lot flamegraphs showing various GType checking overhead. I spent some time diving in and caching the appropriate flags so that we save non-trivial percentage of CPU there.

My nearly decade desire to get rid of GSlice finally happened for most platforms. If you want really a really fast allocator that can do arenas well, I suggest looking into things like tcmalloc.

systemd-oomd

Also out of the Sysprof work came a discovery of systemd-oomd waking up to often and keeping my laptops from deeper sleep. That got fixed upstream in systemd.

Manuals

One prototype I created was around documentation viewers and I’m using it daily now.

I want to search/browse documentation a bit differently than how devhelp and other documentation sites seem to work. It indexes everything into SQLite and manages that in terms of SDKs. Therefore things like cross-referencing between releases is trivial. Currently it can index your host, jhbuild, and any org.gnome.Sdk.Docs Flatpak SDK you’ve installed.

This too is built on libdex and Gom which allows for asynchronous SQLite using GObjects which are transparently inflated from the database.

Another fun bit of integration work was wrapping libflatpak in a Future-based API. Doing so made writing the miners and indexers much cleaner as they were written with fibers.

A screenshot of a new application called Manuals which has a pathbar across the top allowing for rich navigation between and amongst developer SDKs.

Gom

A decade or more ago I made a library for automatically binding SQLite records to GObjects. I revamped it a bit so that it would play well with lazy loading of objects from a result set.

More recently it also saw significant performance improvements around how it utilizes the type system (a theme here I guess).

libpanel

Writing an application like GNOME Builder is a huge amount of work. Some of that work is just scaffolding for what I lovingly consider Big Fucking Applications. Things like shortcut engines, menuing systems, action management, window groups and workspaces, panels, document grids, and more.

A bunch of that got extracted from Builder and put into libpanel.

Additionally I made it so that applications which use libpanel can still have modern GNOME sidebar styling. Builder uses this for its workspace windows in GNOME 46 and contributes to it’s modern look-and-feel.

libspelling

One missing stair that was holding people back from porting their applications to GTK 4 was spellcheck. I already had a custom spellchecker for GNOME Text Editor and GNOME Builder which uses an augmented B+Tree I wrote years ago for range tracking.

That all was extracted into libspelling.

Text Editor

One of the harder parts to keep working in a Text Editor, strangely enough, is task cancellation. I took some time to get the details right so that even when closing tabs with documents loading we get those operations cancelled. The trickier bit is GtkTextView doing forward line validation and sizing.

But that all appears to work well now.

I also tweaked the overview map considerably to make things faster. You need to be extremely careful with widgets that produce so many render nodes which overlap complex clipping. Doubly so when you add fractional scaling and the border of a window can cross two pixels of the underlying display.

A screenshot of Text Editor showing the new overview map which contains a more appropriate amount of padding to appease the renderer's clip tracker.

GNOME Builder got similar performance treatments.

Frame Jitters

While tracking down GTK renderer performance for GTK 4.14 I spent a lot of timing analyzing frame timings in Sysprof.

First, I noticed how Mutter was almost never dispatching events on time and mostly around 1 millisecond late. That got fixed with a timerfd patch to Mutter which tightens that up. At 120hz and higher that extra millisecond becomes extremely useful!

After fixing Mutter I went to the source and made patches taking two different strategies to see which was better. One used timerfd and the other using ppoll(). Ultimately, the ppoll() strategy was better and is now merged in GLib. That will tighten up every GLib-based application including the GdkFrameClock.

I also added support for the Wayland presentation-time protocol in GTK’s Wayland back-end so that predicted frame times are much more accurate.

GLib

In addition to the ppoll() work above in GLib I also did some work on speeding up how fast GMainContext can do an iteration of the main loop. We were doing extraneous read() on our eventfd each pass resulting in extra syscalls. I also optimized some GList traversals while I was there.

I separated the GWeakRef and GWeakNotify lists inside of GObject’s weak ref system so that we could rely on all pointers being cleared before user callback functions are executed. This predictability is essential for building safety at higher levels around weak references.

GtkSourceView

There were a few more cases in GtkSourceView that needed optimization. Some of them were simpler like premixing colors to avoid alpha blends on the GPU. In the end, applications like Text Editor should feel a lot snappier in GNOME 46 when combined with GTK 4.14.1 or newer.

GTK

I spent some time at the end of the 46 cycle aiding in the performance work on NGL/Vulkan. I tried to lend a hand based on the things I remember helping/hurting/doing nothing while working on the previous GL renderer. In all, I really like where the new NGL/Vulkan renderers are going.

While doing some of that work I realized that finalizing our expired cache entries of lines for the textview was reducing our chance at getting our frames submitted before their deadline. So a small patch later to defer that work until the end of the frame cycle helps considerably.

Another oddity that got fixed was that we were snapshotting textview child widgets (rarely used) twice in the GtkTextView thanks to an observant bug reporter. This improved the gutter rendering times for things like GtkSourceView.

I also tried to help define some of the GtkAccessibleText API so we can potentially be lazy from widget implementations. The goal here is to have zero overhead if you’re not using accessibility technology and still be fast if you are.

I also added a fast path for the old GL renderer for masked textures. But that never saw a release as the default renderer now that NGL is in place, so not such a big deal. It helped for Ptyxis/VTE while it lasted though.

GSK also saw a bunch of little fixes to avoid hitting the type system so hard.

libpeas 2.0

Libpeas got a major ABI bump which came with a lot of cleaning up of the ABI contracts. But even better, it got a GJS (SpiderMonkey) plugin loader for writing plugins in JavaScript.

GNOME Builder uses this for plugins now instead of PyGObject.

VTE Performance

As my monitor resolution got higher my terminal interactivity in Builder was lessened. Especially while on Wayland. It got to the point that latency was causing me to miss-type frequently.

Thankfully, I had just finished work on Sysprof so I could take a look.

Builder is GTK 4 of course, and it turned out VTE was drawing with Cairo and therefore spending significant time on the CPU drawing and significant memory bandwidth uploading the full texture to the GPU each frame.

Something I did find funny was how up in arms people got about a prototype I wrote to find the theoretical upper bounds of PTY performance for a terminal emulator. How dare I do actual research before tackling a new problem domain for me.

In the end I finally figured out how to properly use GskTextNode directly with PangoGlyphString to avoid PangoLayout. A trick I’d use again in GtkSourceView to speed up line number drawing.

Along with modernizing the drawing stack in VTE I took the chance to optimize some of the cross-module performance issues. VTE performance is in pretty good shape today and will certainly get even better in it’s capable maintainers hands. They were extremely friendly and helpful to a newcomer showing up to their project with grand ideas of how to do things.

GNOME Terminal

To validate all the VTE performance work I also ported the venerable GNOME Terminal to GTK 4. It wasn’t quite ready to ship in concert with GNOME 46 but I’m feeling good about it’s ability to ship during GNOME 47.

Ptyxis

For years I had this prototype sitting around for a container-based terminal built on top of the Builder internals. I managed to extract that as part of my VTE performance work.

It’s a thing now, and it’s pretty good. I can’t imagine not using it day-to-day now.

A screenshot of Ptyxis showing two tabs, one of the host container, and one of a fedora 40 toolbox container.

VTE Accessibility

Now that I have a GTK 4 terminal application to maintain the only responsible thing for me to do is to make sure that everyone can use it. So I wrote a new accessibility layer bridging VteTerminal to GtkAccessibleText in GTK.

Podman and PTY

While working on Ptyxis I realized that Podman was injecting an extra PTY into the mix. That makes foreground process tracking extremely difficult so I advocated the ability to remove it from Podman. That has now happened so in future versions of Ptyxis I plan to prod Podman into doing the right thing.

GNOME Builder

All sorts of nice little quality of life improvements happened in Builder. More control over the build pipeline and application runtime environment make it easier to integrate with odd systems and configurations.

The terminal work in Ptyxis came back into Builder so we got many paper cuts triaged. You’ll also notice many new color palettes that ship with Builder which were generated from palettes bundled with Ptyxis.

Memory usage has also been reduced even further.

A screenshot of Builder showing the new sidebar styling in the Fishtank color style.

Biased Ref Counting

I wrote an implementation of Biased Ref Counting to see how it would perform with GObject/GTK. Long story short the integration complexities probably out-weigh most of the gains.

Removing Twitter, Mastodon, and Matrix

I removed my social media accounts this year and it’s lovely. Cannot recommend it enough.

Debug Builds and GPUs

Decades ago, when you wanted to run debug builds for UI applications, things were incredibly slow.

First you’d wait minutes for the application to present a window. Then wait tens of seconds for each frame to render. You were extremely lucky if Valgrind caught the issue while you exercised the UI.

Things have gotten much better due to movement in two different directions.

In one direction GCC and Clang got compiler integration for sanitizers like ASAN. Instead of relying on extreme amounts of emulation in Valgrind compilers can insert the appropriate checks, canaries, and memory mapping tricks to catch all sorts of behavior.

In the other direction we’ve started drawing modern UI toolkits with the GPU. The idea here is that if the work is dispatched to the GPU, there is less for the CPU to run and therefore less work for the sanitizers and/or Valgrind to do.

Don’t let that fool you though. A lot of specialized work is done on the CPU still to allow those GPUs to go fast. You trade off framebuffer updates and huge memory bus transfers for more complex diffing, batching and reordering operations, state tracking, occasional texture uploads, and memory bandwidth for vertex buffers and the likes.

Here I’ve compiled all the hot parts of a GTK application with the address sanitizer. That includes GLib/GIO/GObject, Harfbuzz, Pango, and GTK. The application is also running with GSK_DEBUG=full-redraw to ensure we redraw the entire window every single frame with full damage. We use GDK_DEBUG=no-vsync to let it run as fast as it can rather than block waiting for the next vblank.

And still, GTK can produce hundreds of frames per second.

Truly magical.

A screenshot of gtk4-widget-factory with the FPS counter showing 354.40.

BOLT’ing Libraries

I did a little experimenting with BOLT today to optimize libraries post-link.

I’m not an expert on it or anything, but it seems to allow you to reorder functions in your executable/library based on feedback from perf record and some special post-processing. You can merge multiple runs together in case you have different workloads you’d like to optimize for. But in the end, hot functions get placed near each other to reduce instruction cache pressure.

In all, it says you can expect gains up-to about 7% which fits in line with my experiment. For example, I open gnome-text-editor with a large C file, the overview map enabled, and syntax highlighting on. Then hold down Page Down to the bottom, Page Up to the top, and then Page Down back to the bottom.

The first pass through the source code is usually a little slower because you’re doing the incremental syntax-highlighting process.

After using BOLT on Pango, I saw roughly a 6% reduction in time spent measuring text (which is one of the most expensive parts of the overview map).

To test this out, I did have to play with my CFLAGS to have -Wl,--emit-relocs linker option. After that and a meson setup --wipe $SRCDIR things seem to work as expected.

Trying it For Yourself

sudo dnf install llvm-bolt perf

perf record -e cycles:u -j any,u -o perf.data -- gnome-text-editor

# you can do this for any of the binaries
perf2bolt -p perf.data -o perf.fdata ~/.jhbuild/lib/libpango-1.0.so

llvm-bolt ~/.jhbuild/lib/libpango-1.0.so -o libpango-1.0.so.bolt -data=perf.fdata -reorder-blocks=ext-tsp -reorder-functions=hfsort -split-functions -split-all-cold -split-eh -dyno-stats

mv libpango-1.0.so.bolt ~/.jhbuild/lib/libpango-1.0.so

Rinse and repeat.

Frame Pointers in the Media

BPF Performance Tools author and all around profiling expert Brendan Gregg wrote a blog post that sums up what was in my Fedora Magazine article quite well.

Though he has this to say on the topic of Fedora who made this ground breaking change and Ubuntu who followed along afterwards:

The main users of this change are enterprise Linux. Back-end servers.

Which is true in the sense of absolute numbers. But I must say it’s been extremely valuable on the desktop.

I can’t imagine having contributed to making VTE (a code-base I was unfamiliar with) twice as fast without it. Especially when that work happened over the course of about two weeks. It’s so much easier to do performance work when one monitor has usable profiler flamegraphs and the other code.

The wash/rinse/repeat cycle has gotten really good on Fedora and our performance future is bright.

Improving poll() timeout precision

Recently I was looking at a VTE performance issue so I added a bunch of Sysprof timing marks to be picked up by the profiler. I combined that with GTK frame timing information and GNOME Shell timing information because Sysprof will just do that for you. I noticed a curious thing in that almost every ClutterFrameClock.dispatch() callback was rougly 1 millisecond late.

A quick look at the source code shows that ClutterFrameClock uses g_source_set_ready_time() to specify it’s next deadline to awaken. That is in µsec using the synchronized monotonic clock (CLOCK_MONOTONIC).

Except, for various reasons, GLib still uses poll() internally which only provides 1 millisecond timeout resolution. So whatever µsec deadline was requested by the ClutterFrameClock doesn’t really matter if nothing else wakes up around the same time. And since the GLib GSource code will always round up (to avoid spinning the CPU) that means a decent amount late.

With the use of ppoll() out of question, the next thing to use on Linux would be a timerfd(2).

Here is a patch to make GLib do that. I don’t know if that is something we should have there as it will create an extra timerfd for every GMainContext you have, but it doesn’t seem insane to do it there either.

If that isn’t to be, then here is a patch to ClutterFrameClock which does the same thing there.

And finally, here is a graph of how the jitter looks when not using timerfd and when using timerfd.

A graph comparing the use of timerfd in ClutterFrameClock. Before, there is an erratic line jumping many times between 100usec and 1000usec. After, the line is stable at around 10usec.

Accessibility in Ptyxis

First off, what the heck is Ptyxis?

Ptyxis is the new name of what was formerly Prompt. The extremely nice people at Panic let me know they had a product that might be confused with Prompt and I agreed it could be confusing. Thankfully, their office is a few miles from me in Portland so I had a chance to meet the team face to face!

We found a lot to talk about, especially when it comes to text rendering with the GPU and people getting aggressive over their beloved fonts.

Hopefully, you like the new name. If not, feel comfort knowing that the desktop spec has support for GenericName and Keywords allowing you to type whatever you like to find the application.

The application icon for Ptyxis which contains what looks like a keyboard key covered in leaves and an insertion caret.

In the North, Spring is about to burst which is a great reason to learn what Ptyxis is. You can even find a beautiful example of it on the cover of The Linux Programming Interface which if you’re into terminals and Linux you should already own.

Now, the new bit.

Accessibility is super important for many reasons I shouldn’t need to repeat.

I put together an implementation of the new GtkAccessibleText for VteTerminal and am bundling it with Ptyxis to get more testing.

Since this has the potential to really overload the screen reader as the final interfaces are figured out, it’s gated behind a toggle in preferences. I also am hesitant to enable it by default until we have a way in the a11y stack to be extremely lazy about initialization. I don’t want to waste a lot of CPU cycles tracking changes only for them to be sent to an accessibility D-Bus where nobody is listening.

Hopefully this is one less thing preventing Linux distributions from shipping a GTK 4 based terminal emulator by default.

A screenshot of Ptyxis preferences in the Behavior section. The toggle to enable Screen Reader is on. A terminal window says Computer, read this text.

Performance Profiling for Fedora Magazine

I’ve authored an article recently for Fedora Magazine on Performance Profiling in Fedora.

It covers both the basics on how to get started as well as the nitty-gritty details of how profilers work. I’d love for others to be more informed on that so I’m not the only person maintaining Sysprof.

Hopefully I was able to distill the information down a bit better than my typical blog posts. If you felt like those were maybe too difficult to follow, give this one a read.

Sidebars in Libpanel

One of the more recent design trends in GNOME has been the use of sidebars. It looks great, it’s functional, and it gives separation of content from hierarchy.

A screenshot showing a number of GNOME applications which contain sidebars including Nautilus, D-Spy, Control Center, and Calendar. The image contains both light and dark variants split by a line from lower left to upper right of the image.

Builder, on the other hand, has been stuck a bit closer to the old-hat design of IDEs where the hierarchy falls strictly from the headerbar. This is simply because libpanel was designed before that design trend. Some attempt was made in Builder to make it look somewhat sidebar’ish, but that was the extent of it given available time.

A screenshot of the GNOME 45 release of Builder where the headerbar is across the top and panels, documents, and project panel below.

Last week I had a moment of inspiration on a novel way we could solve it without uprooting the applications which use libpanel. You can now insert edge widgets in PanelDockChild which are always visible even when the child is not. Combining that with being able to place a headerbar inside a PanelDockChild along with your PanelFrames means you can get something that looks more familiar in modern GNOME.

A screenshot of what will become Builder for GNOME 46 which includes the common sidebar styling.

If you’d like to improve things further, you know where to find the code.

Faster Numbers

The venerable GtkSourceView project provides a GtkWidget for various code languages. It has a number of features including the most basic, showing a line number next to your line of text.

A screenshot of GNOME Text Editor with line numbers enabled containing the file gtktextbuffer.c.

It turns out that takes a lot more effort than you might think, particularly when you want to do it at 240hz with kinetic scrolling on crappy hardware that may barely have enough engine for the GL driver.

First, you need to have the line number as a string to be rendered. For a few years now, GtkSourceView has code which will optimizes the translation from number to strings with minimal overhead. If you g_snprintf(), you’re gonna be slow.

After that you need to know the X,Y coordinate of the particular line within the gutter and it’s line height when wrapped. Then you need to know the measured pixel width of the line number string. Further still you need the xalign/yalign and xpad/ypad to apply proper alignments based on application needs. You may even want to align based on first line, last wrapped line, or the entire cell.

In the GtkSourceView 5.x port I created GtkSourceGutterLines which can cache some of that information. It’s still extremely expensive to calculate but at least we only have to do it once per-frame now no matter how many GtkSourceGutterRenderer are packed into the GtkSourceGutter.

After that, we can create (well recycle) a PangoLayout to setup what we want to render. Except, that is also extremely expensive because you need to measure the contents and go through a PangoRenderer for each line you render.

If you are kinetic scrolling through a GtkSourceView with something like a touch pad there is a good chance that a decent chunk of CPU is wasted on line numbers. Nicht gut.

Astute readers will remember that I spent a little time making VTE render faster this cycle and one of the ways to do that was to avoid PangoLayout. We can do the same here as it’s extremely simple and controlled input. Just cache the PangoGlyphInfo for 0..9 and use that to build a suitable PangoGlyphString. Armed with a PangoFont and said string, we can use gsk_text_node_new() and gtk_snapshot_append_node() instead of gtk_snapshot_render_layout().

A quick hour or so later I have given you back double digit CPU percentages but more importantly, smoother and lower latency input.

Sysprof makes it easy to locate, triage, and verify performance fixes.

A flamegraph showing that the line number gutter renderer in GtkSourceView was an extremely complex code path.

A flamegraph showing that line number rendering is now a very simple code path.

That said, in the future, if I were redesigning something to replace all of this I’d probably just use widgets for each line number and recycle them like GtkListView. Then you get GtkWidget render node caching for free. C’est la vie.

Prompt

Prompt is a terminal that marries the best of GNOME Builder’s seamless container support, the beauty of GNOME Text Editor, and the robustness of VTE. I like to think of it as a companion terminal to Builder.

Though it’s also useful for immutable/container-oriented desktops like Fedora Silverblue or Project Bluefin where containers are front-and-center.

A screenshot of Prompt with a menu open showing a list of available containers to spawn a new terminal shell within.

This came out of a prototype I made for the GNOME Builder IDE nearly a decade ago. We already had all the container abstractions so why not expose them as a Terminal Workspace?

Prompt extracts the container portion of the code-foundry into a standalone program.

My prototype didn’t go anywhere in recent years because I was conflicted. I have a high performance bar for software I ship and VTE wasn’t there yet on Wayland-based compositors which I use. But if you frequent this blog you already know that I reached out to the meticulous VTE maintainers and helped them pick the right battles to nearly double performance this GNOME cycle. I also ported gnome-terminal to GTK 4 which provided me ample opportunity to see where and how containers would integrate from an application perspective.

I designed Prompt to be Flatpak-first. That has design implications if you want a robust feature-set. Typically an application is restricted to the PID and PTY namespace within the Flatpak sandbox even if you’re capable of executing processes on the host. That means using TTY API like tcgetpgrp() becomes utterly useless when the kernel ioctl() returns you a PID of 0 (as it’s in a different namespace). Notably, 0 is the one value tcgetpgrp() is not documented to return. How fun!

To give Prompt the best chance at tracking containers and foreground processes a prompt-agent runs from the host system. It is restricted to very old versions of GLib/GObject/GIO and JSON-GLib because we know that /usr/bin/flatpak will already require them. Using those libraries instead of certain GLibc API helps us in situations where GLibc is only backward-compatible and not forwards-compatible. Combined with point-to-point D-Bus serialization on top of a socketpair() we have a robust way to pass file-descriptors between the agent and the UI process and we’ll use that a bunch.

There are a lot of little tricks in here to keep things fast and avoid energy-drain. For example, process tracking is done with a combination of exponential-backoff and re-triggering based on either new content arriving or certain key-presses. It gives a very low-latency feeling to the sudo/ssh feature I love from Console, albeit with less overhead.

One thing I find helpful with Builder is that when I come back to it my project session is right there. So this has session support too. It will restore your tabs/containers how they were before. So if you have a similar workflow you might find that useful. If not? Just turn it off in Preferences.

I want to have a bit of fun because right now I’m stuck indoors caring for my young, paraplegic dog. So it’s packed full of palettes you can play with. Who doesn’t like a little color!

A screenshot of Prompt with the preferences window open allowing the selection of numerous palettes with diverse color sets. The terminal window is recolored using colors from the palette.

There are some subtle performance choices that make for a better experience in Prompt. For example, I do like having a small amount of padding around the terminal so that rounded corners look nice and also avoids an extra framebuffer when rendering on the GPU. However, that looks odd with scrollback. So Prompt rewrites the snapshot from VTE to remove the background and extraneous clipping. We already have a background from window recoloring anyway. It’s a small detail that just feels good when using it.

Another subtle improvement is detecting when we are entering the tab overview. Currently, libadwaita uses a GtkWidgetPaintable to represent the tab contents. This works great for the likes of Epiphany where the contents are backed by GL textures. But for a terminal application we have a lot of text and we don’t want to redraw it scaled as would happen in this case. That puts a lot of pressure on the glyph cache. So instead, we create a single texture upfront and scale that texture. Much smoother.

For people writing terminal applications there is a little inspector you can popup to help you out. It can be difficult to know if you’re doing the right thing or getting the right behavior so this might be something we can extend going forward to make that easier for you. GTK’s inspector already does so much so this is just an extension of what you could do there.

A terminal window open with a secondary "inspector" window open. The inspector shows what column and row the mouse is positioned as well as the cursor and what non-visible OSC hyperlink is under the pointer.

Creating Prompt has elevated problems we should fix.

  • Podman creates an additional PTY which sort of breaks the whole notion of foreground processes. Filed an issue upstream and it seems likely we can get that addressed for personal containers. That will improve what happens when you close your terminal tab with something running or if you SSH’d into another host from the container.
  • Container tracking is currently limited to Fedora hosts because toolbox only emits the container-tracking escape sequences when the host is Fedora. The current goal I’ve discussed with VTE maintainers is that we’ll use a new “termprop” feature in VTE that will be silently dropped on terminal applications not interested in it. That way toolbox and the likes can safely emit the escape sequence.
  • Currently podman will exit if you pass a --user or --workdir that does not exist in the container. That isn’t a problem with toolbox as it is always your user and fails gracefully for directories. So we need a good strategy to see if both of those are available to inherit when creating new tabs.
  • This does have transparency support, but it’s hidden in a GSetting for now. Once we have libadwaita with CSS variable support we can probably make this look better during transitions which is where it falls down now. We also need some work on how AdwTabOverview does snapshots of tabs and inserts a background.
  • I have a .bashrc snippet to treat jhbuild as a container which is helpful for those of us GNOME developers still using it.
  • Accessibility is an extremely important piece of our infrastructure in GNOME. So part of this work will inevitably tie into making sure the a11y portion of VTE works with the soon-to-land a11y improvements in GTK. That has always been missing on GTK 4-based VTE and therefore every terminal based upon it.
$ flatpak install --user --from https://nightly.gnome.org/repo/appstream/org.gnome.Prompt.Devel.flatpakref

If you like software that I write, consider donating to a pet shelter near you this holiday season. We’re so lucky to have great pet care in Oregon but not everywhere is so lucky.

Happy Holiday Hacking!