August 30, 2023

Viewing Images in GNOME: Loupe and Glycin

Loupe is GNOME’s new Core app for viewing images. Starting with the GNOME 45 release, you might find it as Image Viewer on your system. It replaces the previous image-viewing app Eye of GNOME. In honor of this historic occasion, I wanted to give a bit of insight into the making and technology of Loupe.

Why Start from Scratch

The first documented commits to Eye of GNOME (EOG) are from September 1999 by Federico. Some of this code from back then withstands the test of time until today. Likewise, the image loading was already powered by GdkPixbuf, which is still GNOME’s image loading library today. So why start replacing such a well-tried set of software now?

App window with a minimalist headerbar on top and an image of the Carina nebula below.
Loupe provides more than just a refreshed user interface.

Original experiments for writing a new image viewer go back to 2020. There wasn’t a complete technical plan for Loupe, but rather a gut instinct with everyone involved that for the goals of Loupe, a rewrite would be the better solution. In retrospect, this was the right decision. With the port to GTK 4, a new interface design, gestures and animations, sandboxed image loading, and using more upstream projects for decoding tasks, the code overlap wouldn’t have been too large to begin with. On top, the Rust language is a good fit for handling media data since it provides memory safety and performance at the same time.

Hidden Features

On the surface, Loupe looks simple. And that’s what we want it to feel like. Behind this, however, are a lot of features that make this possible. Here are a few examples that highlight the differences to EOG. The image rendering is now happening on the graphics card. That allows viewing quite large images while retaining a responsive interface, including animations and direct visual feedback for touchpad and touchscreen gestures. It also makes Loupe much more snappy on high-resolution screens, especially noticeable with higher refresh rates. To keep image navigation for SVGs smooth as well, Loupe uses “tiled rendering,” only rendering relevant parts of the SVG and providing those tiles to the graphics card for combining them. This allows Loupe to render large SVGs while keeping the interface responsive. Loupe also supports High DPI screens better in the way that a 100 % image size is oriented on the physical amount of pixels. For transparent images, Loupe analyzes the image’s content and chooses a different background color to not end up drawing black content on a black background and alike.

Dialog with layout options to the left an a print preview to the right showing a Pika.
Not all features new to Loupe are as apparent as the print layout dialog.

Last but not least, I wrote a new wrapper around the image decoding process. The project is named Glycin after a photographic developing agent. Glycin sandboxes the image loading, which potentially¹ adds security and allows a flawed image loader to crash without pulling Loupe down with it.² Additionally, most decoders Glycin uses are written in Rust, adding an additional line of defense via memory safety.

Rust Doesn’t Solve Everything

Duh! I know. But sometimes I feel like there is a tendency that one might forget that besides all the struggle with programming languages and build systems, programming in itself also remains hard. No matter the tools one uses. While the codebase at hand is still relatively small – about 8000 lines of Rust code – there were a bunch of logic and design challenges that required a day without brain fog³. One example is the combination of many input methods and animations and their potential interactions and always getting them represented correctly on the screen.

Another big part of this project was, as so often, working with upstream projects that make all the magic possible in the first place. The GTK maintainers took on the battle with OpenGL to add mipmapping support for better texture rendering. I even dipped my toes into GTK’s texture code myself to add support for gray-scale textures, apart from some smaller additions. We also discussed future GTK APIs for HDR images and ICC profiles in a call. Libadwaita also got some features needed for Loupe. The image decoding crates got a bunch of commits to add support for ICC color management profiles. For the libheif bindings, the maintainer took care of most of the needed changes.

I learned a bunch of new things during the development. About GTK internals, custom widgets, the dreaded OpenGL, image formats, color spaces, memfd, and more.

What will Break

Now, with replacing and changing so many bits an pieces, what will break? First of all, there are some limitations when using Loupe via Flatpak. This, however, was already the case with EOG. Using hardware acceleration also means that your graphics drivers have to work. In some rare occasions, like with the PinePhone (non-pro), this seems to be an issue. Switching to software rendering in those cases is possible, however. The tiled rendering code is not working as well as I would like it to. This can, for example, result in SVGs being rendered slightly blurry while using a pinch gesture to zoom. There can also be a short delay in animations for very large images or on older hardware. I did not have the time to properly address the issue for this release.

We are also replacing many of the image decoders with new ones. There are cases where the new image loading has better support for formats, and there are images that can’t be opened at all but work in EOG. The reason is mostly that many image standards have parts that have rarely been used in practice, and therefore, almost no image decoder implementation is 100 % complete. I’m sure that we will find some formats that are used in practice, and support has to be added in future versions. Maybe the fax encoding standards for TIFF?

We are shipping Loupe in its current state as a Core component since we are convinced that the benefits will outweigh the minor regressions we might hit. We also had the chance to confirm this through the feedback we got over the complete development and incubation period.

Closing Thoughts

Most of Loupe has been written over a timespan of nine months, with me also having to move flats during this time. None of the people who directly contributed to Loupe were paid for their work. On one hand, it is promising that we can pull such a project off as a community effort. On the other hand, considering how many companies will rely on such GNOME Core components, the balance does look off to me.

Big thanks go out to everyone who helped make this project happen: Chris, who started the whole project. Allan, for all the design work. Alice, Ivan, Zander, for a huge amount of help in all areas, especially touch gestures, and lower-level plumbing, and … even more gestures! The contributors to all the countless projects Loupe depends on. And everyone who helps with testing and is reporting issues.

The development of Loupe hasn’t been without friction. With the release of GNOME 45, I will be stepping down from maintaining Loupe to not stand in the way of the original ideas of how to run this project.


¹ While decoder processes don’t have write access to files and no network connection, I still consider the hardening of the sandbox in its infancy.

² For those who are interested in the technical details: Glycin spawns one sandboxed process per image file via bwrap or flatpak-spawn. The communication happens via peer-to-peer D-Bus over UNIX socket. The file data is read from a GFile and sent to the sandbox via a separate UNIX socket. The texture data is provided from the sandbox via a memfd that is sealed afterward and given as an mmap to GTK. For animations and SVGs the sandboxed process is kept alive for new frames or tiles as long as needed. For an introduction to this structure, see Christian Hergert, “Designing Multi-Process Application Security,” GUADEC 2019.

³ Developing software with chronic illness and disability would probably be worth a blog post on its own.

August 29, 2023

rpmlint updates (August 2023)

We are at the end of the summer and this means that this year Google Summer of code is ending.

The recent changes applied now in the main branch include:

  • Remove usage of pkg_resource because it's deprecated.
  • Fix elf binary check with ELF files with a prefix.
  • New check for python packages with multiple .pyc files for different python versions.
  • Improve the testing framework (merged the work done during the GSoC 2023)

Summer of Code 2023 updates

The summer of code is ending and the work done by Afrid was good enough to be merged, so I merged it the past week.

I'm really happy with the work done during the GSoC program, now we've a more simple way to define tests for rpmlint checks mocking the rpm, so it's not always needed to build a fake rpm binary for each new test. This will make a lot easier to create simple tests, so I hope that we can increase the code coverage using this new framework.

During this time Afrid has extended the FakePkg class, so it's possible now to define fake metadata and files with fake tags and attributes. It's not complete and it's not a simple task to replace all the rpm binaries used for tests, because the Pkg class and RPM tags is a complex thing, but the current state allow us to replace a lot of them. Afrid has replaced some of the tests that uses binaries, but in the following months we can continue working on this and replace more.

After this work, we can now start to use more the FakePkg class in tests, so another task that we can do is to provide some common fake pkgs to use in different tests and new checks, so now it's possible to create fake packages with dynamic random data, so we can extend tests with fuzz testing and maybe this will help to improve the tool reliability.

Conclusion

I've participated as mentor several times now in the summer of code, and outreachy, and almost always was a good experience. With the gnome foundation in previous programs and this year with opensuse. These two communities are very open to collaboration and makes the whole process really simple, for me as mentor, and also for the intern.

I want to congratulate Afrid, because it was nice to work with him during this summer, he has done a great work, not just technically, but communicating, asking and finding his own solutions without requiring a continuous guidance.

He is very passionate and looks like a nice person, so I hope that he will continue around the open source, it could be opensuse, rpmlint or any other community, but this kind of people is what you want to find in any community.

After many years collaborating with different free software communities, it's amazing that there are so many great people in every project, of course you can find toxic communities and people, but in my experience, that's usually just noise, there are a lot of nice people out there, doing a great work, and I'm happy that young people like Afrid can be part of the free software movement, because this is what makes the free software great, the people that is working on it.

So Thanks a lot to Google for another summer of code, thanks to SUSE for letting me, and encourage me, to mentor, and thanks to all the free software developers that are out there.

I encourage everyone to participate in this kind of programs, for interns, it's a good opportunity to learn and to make some money working on free software, for mentors it's an opportunity to get some help in your project and help newcomers to be part of the community.

Have a lot of fun!

Portfolio 1.0.0

I am happy to announce the release of Portfolio 1.0.0! This new release is the first step in the modernization process to GTK 4 and Libadwaita. It’s also a continuation to my efforts of bringing a minimalist file manager to the mobile Linux community, with a few important bug fixes.

As a starting point for the modernization process, this new version of Portfolio preserves the exact same design, in a GTK 4 flavor. A few reasons for that.

First, although I wish everyone was distributing applications using Flatpak, I want to reduce friction for the mobile Linux distributions, by sticking to currently available APIs. Second, I want to spend more time experimenting with newer Libadwaita widgets, specially with the ones from the upcoming 1.4 release, as these could require redesigning a few aspects of Portfolio’s graphical interface.

On the maintenance side of things, I fixed a major issue which prevented some files to be sent to the trash and another that prevented displaying progress while moving files, among other minor fixes. Porting widgets to Libadwaita fixed one annoying issue which prevented swapping back, so that’s a big free win.

Even before all of this work ever happened, I spent some time refactoring the project into smaller, more modular, pieces. This helped significantly to the porting process and it will ensure an easier maintenance move forward. So expect more refactors to come.

Unix sockets, Cygwin, SSH agents, and sadness

Work involves supporting Windows (there's a lot of specialised hardware design software that's only supported under Windows, so this isn't really avoidable), but also involves git, so I've been working on extending our support for hardware-backed SSH certificates to Windows and trying to glue that into git. In theory this doesn't sound like a hard problem, but in practice oh good heavens.

Git for Windows is built on top of msys2, which in turn is built on top of Cygwin. This is an astonishing artifact that allows you to build roughly unmodified POSIXish code on top of Windows, despite the terrible impedance mismatches inherent in this. One is that until 2017, Windows had no native support for Unix sockets. That's kind of a big deal for compatibility purposes, so Cygwin worked around it. It's, uh, kind of awful. If you're not a Cygwin/msys app but you want to implement a socket they can communicate with, you need to implement this undocumented protocol yourself. This isn't impossible, but ugh.

But going to all this trouble helps you avoid another problem! The Microsoft version of OpenSSH ships an SSH agent that doesn't use Unix sockets, but uses a named pipe instead. So if you want to communicate between Cygwinish OpenSSH (as is shipped with git for Windows) and the SSH agent shipped with Windows, you need something that bridges between those. The state of the art seems to be to use npiperelay with socat, but if you're already writing something that implements the Cygwin socket protocol you can just use npipe to talk to the shipped ssh-agent and then export your own socket interface.

And, amazingly, this all works? I've managed to hack together an SSH agent (using Go's SSH agent implementation) that can satisfy hardware backed queries itself, but forward things on to the Windows agent for compatibility with other tooling. Now I just need to figure out how to plumb it through to WSL. Sigh.

comment count unavailable comments

Misconceptions About Immutable Distributions

What Is an Immutable Distribution?

“Immutable” is a fancy way of saying unchangeable. So, an immutable distribution is essentially an unchangeable distribution, i.e. read-only. However, in most cases, the entirety of the distribution is not immutable. Some directories, like /tmp are writable, as some directories need write capabilities to actually function. Some distributions may even have other directories, such as /etc, writable for extra functionality.

To redefine, an immutable distribution is when, at a minimum, only the core of the distribution is read-only, and the rest is read-write.

Note: The more accurate terms are “reprovisionable” and “anti-hysteresis”. However, since “immutable” is more commonly used, I’ll use it instead throughout this article. Further reading: “Immutable” → reprovisionable, anti-hysteresis by Colin Walters.

Technical Definitions

There are a few technical definitions that should be understood before reading the rest of the article:

  • Rollback (rolling back): The process of undoing a change or update. For example, you upgrade your system to the latest version, which introduces bugs. You can then revert that update and go back to the version that worked well.
  • Reproducibility: The process of delivering identical software regardless of the time or system used. Suppose you install a distribution on hundreds of laptops and desktops over the course of a week. The distribution you install on the first desktop (on the first day) will be exactly the same as the last laptop (on the last day). That is, all systems will be identical. You can think of it as a perfect clone.
  • Atomicity (atomic updates): The process of ensuring that a change (update, downgrade, etc.) is either fully applied or aborted. For example, your system shuts down in the middle of an upgrade due to a power outage. Instead of having a half-updated system, the update is fully aborted, meaning that your system was unchanged, just like before you started the update.

Concept Versus Implementations

It’s really easy to confuse the concept and the implementation when discussing immutability.

As we noted above, an immutable distribution is when the core of the distribution is read-only — that’s it. You (or someone else) decide to implement that concept as you see fit. You could have a locked down and inflexible system, like GNOME OS,1 where there isn’t even a system package manager; or you could have something that is designed to be flexible, like NixOS, where you can change the desktop environment, the kernel, the boot loader, and so on.

You can think of an immutable distribution as any traditional distribution. Some traditional distributions are locked down and inflexible, such as Fedora Workstation, where removing or even disabling SELinux can cause problems. Fedora Workstation also prevents you from accidentally removing core packages like sudo. Meanwhile, some traditional distributions are designed to be flexible, such as Gentoo Linux, where you can change almost anything in your system.

Misconceptions

There are several misconceptions both for and against immutable distributions that I would like to address.

I’m going to use pseudo quotes, which means I will be quoting and refining arguments I’ve interpreted from debates and media.

“Needing to Reboot”

Due to the read-only nature of immutable distributions, applying changes (updates, installs, etc.) absolutely requires a reboot.

The need of reboots varies per implementation. With Endless OS, you must reboot to apply updates. On Fedora Silverblue, you can run sudo rpm-ostree apply-live in the terminal to apply changes while the system is booted. On NixOS, you don’t need to reboot at all.

Even then, rebooting your system after updating it is really important to keep it running optimally. As the article Restarting and Offline Updates explains: “The Linux Kernel can change files without restarting, but the services or application using that file don’t have the same luxury. If a file being used by an application changes while the application is running then the application won’t know about the change. This can cause the application to no longer work the same way. As such, the adage that “Linux doesn’t need to restart to update” is a discredited meme. All Linux distributions should restart.

Many immutable distributions focus on reboots when performing system-level updates, because in many cases a reboot is crucial for stability. That’s why Android, iOS, macOS, Windows, ChromeOS, etc. require a reboot.

“Difficult to Use”

Since immutable distributions are locked down, inflexible, and often need workarounds for various issues, they’re more difficult to use.

Difficulty varies per implementation and users’ priorities. For me, Fedora Silverblue is really easy to use, because it provides a great experience by default;2 I want something that is reliable, gets out of my way as much as possible, and follows closest with my political views (I value free software, inclusiveness in the community, and progressing the Linux desktop), which Fedora Silverblue is one of the few that provides.3 However, NixOS isn’t for me, because I’m not a power user.

Endless OS is used by many educational institutions, because these institutions typically need a system that is simple, accessible and secure. On the contrary, NixOS is a powerful system typically used by power users and organizations that absolutely need the features it offers.

However, it’s worth noting that, no matter the distribution, immutable or not, workarounds may be needed. Nothing is perfect, but there are many cases when external developers refuse to collaborate. For example, many drivers are unavailable in the Linux kernel, so we’re sometimes forced to inconveniently install these drivers manually. While a distribution like Arch Linux has the AUR, most other traditional distributions do not have the luxury of installing obscure drivers. In short, this isn’t a fundamental problem with immutable distributions; it’s a problem that you have with every distribution.

“Inflexible by Nature”

Due to the read-only nature of immutable distributions, users cannot tinker with their system.

Again, this heavily depends on the implementation. If we look at traditional distributions, Ubuntu is not designed to be tinkered with, nor is Fedora Linux. As such, these distributions are prone to breaking after a significant amount of modification.

Meanwhile, NixOS, an immutable distribution, empowers the user and allows them to change many aspects of their system. Don’t like GRUB? Use systemd-boot, rEFInd or something else. You want to add a Mandatory Access Control? Use SELinux. Don’t like SELinux? use AppArmor or something else. Don’t like using GNOME? Use Plasma, XFCE, MATE, Cinnamon, Sway or any desktop environment/window manager. Don’t like the fact that you’re using an LTS version of NixOS? Just switch to an unstable or even a bleeding edge channel. What’s that, you want to go back to LTS? You can. Want to mix and match packages from different channels? Go for it… You could view NixOS as Arch Linux on steroids.

Really, some traditional and immutable distributions are locked down and inflexible, meanwhile some others are flexible. This heavily varies per implementation.

“Immutable Distributions Are More Secure”

Immutable distributions provide better security, because […]

I truly believe that statements like these create a false sense of security because “security” can be interpreted differently by different people. Allow me to identify the meanings of “security” and address each of them:

“Security as a Safety Net”

You can roll back or prevent damage in case an error occurs. For example, your system shuts down in the middle of an update due to a power outage. However, thanks to the system’s atomicity, nothing was changed on the system, thereby allowing you to boot back into the system without a problem.

Atomicity could be replicated on traditional distributions if someone put in the effort.

If the system updates but runs into problems, you can roll back from your bootloader.

Rollbacks can be implemented on traditional distributions. Linux Mint and openSUSE Tumbleweed/Leap are some implementors of this feature.

“Security as a Measure to Prevent Malicious Actors From Compromising Your System”

Thanks to the read-only nature of immutable distributions, they prevent malicious actors from doing more damage than traditional distributions.

While immutable distributions might prevent malicious actors from doing certain things on your system, the bigger problem is that your personal data is at risk. Whether the actor can use sudo is less of an issue than the fact that your GPG keys, personal media and documents, or other sensitive data are easily accessible once compromised.

Whether your system is immutable or not, there are still really important steps you should be taking to make your system more secure. For example, you can use LUKS2 to encrypt your drive(s) to protect your data from physical access, and you can use Flatpak (with Flatseal) to make sure that your data is better protected from malicious/compromised apps. These steps are highly beneficial and should not be taken lightly!

“Security as a Measure to Prevent Users From Breaking Their Own Systems”

Thanks to the read-only nature of immutable distributions, even the most destructive actions, like sudo rm -rf --no-preserve-root /* won’t destroy the system.

While running such commands might prevent total destruction, they can still do enough damage to make the benefit meaningless. In this example, running sudo rm -rf --no-preserve-root /* will delete your $HOME (user) directory if you wait long enough, since the user directory is mutable; depending on the implementation, it could delete your boot partition as well. These destructive commands are usually destructive in other ways on immutable distributions, as the vulnerable bit is the mutable places. Only the immutable parts of the systems are unaffected, but the rest are just as vulnerable as any vulnerable place in traditional distributions.

Immutability Is an Implementation Detail

Immutable and traditional distributions can literally share the same benefits and drawbacks, because, in reality, whether the core of the distribution is read-only or read-write is a minor detail. An immutable distribution could be as unstable as Manjaro and rely on distribution packages, meanwhile a traditional distribution could be as stable as Endless OS and fully embrace containers, such as Flatpak. A traditional distribution could be as locked down and inflexible as GNOME OS, meanwhile an immutable distribution could be as flexible as Gentoo Linux (theoretically).

Immutable distributions are different from each other, the same way traditional distributions are different from each other as well. For example, Vanilla OS utilizes ABRoot, whose concept is inspired from Android’s A/B system updates. Fedora Silverblue and Endless OS utilize OSTree, which is similar to git in paradigm. MicroOS utilizes btrfs’s features. NixOS does, uhh… yes.

While immutable and traditional distributions are presented as different in categories, they have very little difference in nature. It’s best to treat immutable distributions like traditional distributions, rather than a whole different paradigm. It’s up to the maintainers to decide what they want to do with their distribution.

Immutability Relieves Maintenance Burden

Okay, as explored above, if the “benefits” are not benefits that are exclusive to immutable distributions and can be applied to traditional distributions, then why do immutable distributions exist in the first place? Why not just implement atomicity, reproducibility, and rollback functionalities to a traditional distribution?

Simply put, immutable distributions make it much easier for maintainers to focus on their high-level goals, because immutability eases the burden of maintenance.

The benefits are indirect for users, but direct for maintainers. Maintainers have fewer things to worry about than in traditional distributions, which allows them to spend their precious time and resources on the things they actually want to worry about, such as stability, reproducibility, providing a safety net, and so on. As a result, users get a more robust system.

This is why immutable distributions are generally considered “safer” than traditional distributions, even though the concept is not. So far, maintainers have put more effort into making their immutable distribution robust, as opposed to traditional distributions, which usually have to worry about the whole mutable aspect. This benefits immutable distributions greatly, because they don’t have to worry about solving the problems that traditional distributions typically run into.

Similarly, many developers prefer to officially support Flatpak over traditional packaging formats, because they only have to worry about one format as opposed to hundreds. As a result, they have more time and energy to focus on achieving their real goals, instead of focusing on tedious repetitive tasks and supporting a wider range of combinations (packaging format, dependencies version and patches, etc.).

All of these features (reproducibility, atomicity, and rollback) can also be implemented in traditional distributions. However, these features are typically treated as first-class features in immutable distributions, even though they are not mandated, whereas they are second-class features in most traditional distributions that support them. Immutability greatly reduces the maintenance overhead and leaves more room to improve the things maintainers want to improve. Immutable distributions have recently become popular, and are already used and popular in educational and personal systems.

Conclusion

To summarize, an immutable distribution is when the core of the distribution is read-only. Anyone is free to grab that concept and implement a distribution with it. The “benefits” and “inconveniences” that we tend to see with immutable distributions could be applied on traditional distributions, but immutable distributions tend to be easier to accomplish the high-level goals, because they are easier to maintain.

In the end, immutability should be seen as an implementation detail and philosophical/political decision, rather than a purely technical one, because traditional and immutable distributions can literally share the same benefits and drawbacks.

Footnotes

  1. GNOME OS is used for development purposes, so it doesn’t need a package manager. 

  2. Except its downsides

  3. I’m currently looking into Vanilla OS and think about switching to it once 2.0 is released

August 28, 2023

An update on GNOME Settings

There’s no question that GNOME Settings is important to the overall GNOME experience and I feel flattered to share the responsibility of being one of its maintainers. I have been involved with Settings for almost a decade now but only in the last few months I have  started to wear the general maintainer hat “officially”.

That’s why I am compelled to update our community on the current state of the project. Settings is also co-maintained by Robert Ancell who has been doing great work with reviews and also helping us improve our code readability/quality.

The last general update from Settings you might have heard of was Georges’ Maintainership of GNOME Settings Discourse post. Some of what’s written there still holds true: Settings is one of the largest modules in GNOME, and being this hub connecting the Shell, the settings daemons, network manager, portals, cups, etc… it needs more maintainers. It needs especially maintainers with domain expertise. We have a handful of active contributors doing great frontend/UI work,  but we lack active contributors with expertise in the deep dungeons of networking or color management, for example.

To tackle this issue, one of my goals is to improve the developer experience in GNOME Settings to attract new contributors and to enable drive-by contributors to post changes without struggling much with the process. For that, I kickstarted our Developer documentation. It is in an early stage now and welcoming contributions.

I also have been invested in fixing some of our historical UI consistency problems. A lot has been done in the gnome-44 and gnome-45 cycles to adopt the latest design patterns from the GNOME Human Interface Guidelines with libadwaita and modern GTK. Alice Mikhaylenko and Christopher Davis did an outstanding job with the ports to modern Adwaita navigation widgets. We also gained a new “About” panel that can condense more information that is useful especially for debugging/supporting issues. There’s still work to be done on this front especially with certain views that are currently looking a bit out of place in comparison to modern views.

Screenshot of the new "About" panel.

The new Privacy hub is a new “hub” panel introduced by Marco Melorio in gnome-45 that is our initial step towards reducing the overall number of panels.Screenshot of the new "Privacy" panel.For GNOME 46 we want to introduce a new “System” hub panel, developed by our Google Summer of Code intern Gotam Gorabh, as well as introduce a new “Network & Internet” panel that is being already worked on by contributor Inam Ul Haq. These are two epics that involve reworking some complicated panels such as the Wifi/Network and User Accounts ones. These are panels that should also see a big frontend rework in the gnome-46 cycle and that I plan to work on myself.

Also a big thank you to Allan Day, Jakub Steiner, Tobias Bernard, Sam Hewitt, and other folks doing outstanding design and UX work for Settings.

GNOME 45.0 (stable) will be released in September, shipping plenty of new stuff and bugfixes. It would be extremely helpful if you could test the latest changes and report issues and regressions in our issue tracker. GNOME Settings 45.rc has been released and should be available soon in GNOME OS and unstable/development distro releases such as Fedora Rawhide.

If you want to get involved, feel free to join our Matrix chat channel and ask questions there. I also monitor the “settings” Discourse tag, where you can ask support questions and suggest features.

GSoC 2023: Rust and GTK 4 Bustle Rewrite (Final Overview)

Thumbnail

Over the summer, for 12 weeks, I worked on rewriting Bustle in Rust and GTK 4 as part of the Google Summer of Code (GSoC) 2023 program. This post is an overview of the work done and the future plans for the project.

About Bustle

Bustle is a graphical application used to analyze D-Bus activities. It uses sequence diagrams to present signal emissions and method calls messages.

Old Bustle

Bustle represents these messages using a row-based layout. Each row is dedicated to displaying the following information: the elapsed time since the first message, the message path, and the member, which could be the name of the emitted signal or invoked method. On the other hand, individual columns correspond to distinct D-Bus services. Bustle draws arrows that transverse these columns to visualize the communication between services, while arcs represent method invocations and returns. This visual paradigm is valuable as, for instance, it can be used to see which services a D-Bus application talks to and how often, which can be handy in debugging, enhancing security, and optimizing performance within the application.

Project Goal

The ultimate goal of this project is to port Bustle to GTK 4 and rewrite it in Rust. Although the current implementation of Bustle in Haskell and GTK 3 is functional, there are compelling reasons to consider a rewrite in Rust. This includes enabling the tool to take advantage of a range of ergonomic libraries, including zbus, gtk4-rs, and pcap-file, that would ease the burden in maintenance. Furthermore, the growing Rust community and the availability of the Rust SDK in Flathub would make the tool more accessible to potential contributors and simpler to distribute to users.

Porting the tool to GTK 4, on the other hand, would offer several benefits, such as access to newer and more performant widgets and drawing APIs like GtkListView and GskPath. This would allow Bustle to benefit from the latest developments in the platform and remain current with evolving standards.

Altogether, a rewrite of Bustle in Rust and GTK 4 would provide advantages that can enhance the tool's functionality, maintainability, longevity, accessibility, and possibly efficiency.

Work Done

Most of the tasks in the original proposal have been completed. This includes having an initial MVP, porting GDBus usage to zbus, implementing file loading and saving, completing the recording functionality, and porting the UI, which comprises the diagram, details view, and services filter, to GTK 4.

For more details on the work done, there are also bi-weekly updates on my blog.

Snapshots

Diagram and Details View

Diagram and Details View

Recording and Services Filter

Recording and Services Filter

Code Merged

These are the summary of code that has been merged during the GSoC period.

Bustle

Most of the work done is in the Bustle GNOME GitLab repository, where the following pull requests have been merged:

zbus and gtk4-rs

While the project is focused on Bustle, some changes are necessary to be upstreamed to other projects. This includes changes to zbus and gtk4-rs, where the following pull requests have been merged:

zbus

gtk4-rs

Code to be Merged

Due to time constraints and unexpected issues, some of the pull requests are still pending review:

Future Plans

While most of the tasks in the proposal have been completed, there are still a few things that need to be done, including the following:

  1. Optimizing performance
  2. Hunting and squashing bugs:
    • Improving name owner changed signal handling
    • Drawing method call arc regardless if the row is not drawable
    • Properly killing the dbus-monitor process
  3. Adding more features:
    • Adding a button that scrolls to the method call message of a method return message or vice-versa
    • Adding a way to open multiple diagrams at once via tabs and multiple windows
  4. Fixing regressions and releasing the application on Flathub
  5. Continuous involvement and contribution to open-source

Final Words

I have to say this is, so far, the most challenging part of my software development journey. It felt like a hackathon since I had to understand things and create something that works quickly. While it was challenging, it was also rewarding as I was able to learn a lot of things and create something that I am proud of. There were moments of time pressure and frustration, but with experimentation, collaboration, and a lot of reading, I was able to overcome these challenges and make meaningful progress. The complexity of the project pushed me out of my comfort zone, forcing me to delve into unfamiliar areas of programming and technology.

Lastly, I would like to express my gratitude to my mentors, Bilal Elmoussaoui and Maximiliano Sandoval, for tirelessly reviewing my pull requests and guiding me. I would also like to thank the GNOME Foundation and the community, especially the GNOME GSoC admin, Felipe Borges, for giving me the opportunity to work on this project. I would also like to extend my appreciation to zbus maintainer Zeeshan Ali for their help in getting my pull requests merged against zbus. Finally, I would like to thank my family and friends for their support and encouragement.

libopenraw Rust with C API

As I previously talked about, I started porting libopenraw to Rust. It is now in a state where it has more feature than the original.

When I started writing this post, I didn't have 100% of the code Rust, but since I have removed the last bit of C++, for which I had cut corners to make sure to have a functional API for C.

The only C++ code left is the various utilities and the C++ test suite to validate.

The goal

The goal of the Rust rewrite is to have the digital camera raw parsing library written in Rust instead of C++, while still being available with a C API.

The goal of the updated C API is to be close to the original API. However it's also a good time to do some breaking changes. Notably most of the "objects" are now immutable.

Rewrite

I did the rewrite in a branch, aiming to provide the same level of functionality as the C++ code. One of the first step was to rewrite the test suite to use the same data set. Doing so did allow verifying the consistency in behaviour, a progressively implement all the features and formats.

The C++ code use inheritance a bit to override some of the behaviours based on which file format it was. One of the thing most camera raw file have in common is that they are mostly a TIFF file. However Rust doesn't do object-oriented. It has traits that allow implementing some level of polymorphism, but in that case the concept had to be rethought a little bit.

Benefits

This is where Rust shines.

First there are a lot of things Rust, or the ecosystem around, did for me. In C++ I had re-implemented a stream system to allow IO on buffers and handling endianess. In Rust you can just read from a slice (a fixed size array of data) with the Read trait, and there are built-in functions to read endian-specific values. I still have the memory of trying to figure out which include to add depending on the UNIX flavour, between Linux, macOS and the BSD.

Second, the Debug trait. Just derive Debug (it's a simple macro) and you can "print" an type like that, using string formats. In C++, well, it's a lot of work. And on enum types it will print a human readable value, ie the one you write in code.

Third, the safety. A lot of the safety feature of Rust prevent mistake. And at runtime, the bounds checking catch of a lot of things too. Being required to use Option<> or Result<> to handle cases where in C++ you'd get some null value.

At that point, a few bugs I identified porting / rewriting the code made their way to the 0.3.x series in C++.

Drawbacks

libopenraw requires a Rust compiler (technically it already did, but I know some packagers that did disable Rust1. This mean that non current architecture might not be supported. To me it's not a big issue, I target modern computers.

Testing

As mentioned I rewrote the test suite with the same data set. This is an essential part of making sure the features work as previously.

I also rewrote ordiag and the dumper ordumper. The dumper allow analyzing the structure of the file, and I didn't have this one in C++ (instead I had a more primitive exifdump). The dumper is heavily inspired from exifprobe that has served me well and that I forked. Really I can see the amount of work I didn't need to do with Rust.

To add to this I wrote libopenraw-viewer, a small Rust application to view the content of camera raw files. This allow much more easily to see the output. This has helped me to find fundamental bugs in some of the parsing that led to some fixes "upstream", namely into the C++ version (0.3.x branch). I should have done that a long time ago. This also allow me to test the Rust API.

C API adaptation

Last but not least I had to provide a C API. This allow using the library.

Rust to C ffi has limitations. You can basically pass numbers, pointers, and eventually simple structures.

The libopenraw API did return several types of "ref" which are just pointers to opaque types. The idea was always to only have opaque types through the API, which internally were C++ instances.

One of the key change is that the only object that can be explicitly be created with the API is ORRawFileRef, because it's the entry point. Very few need to be released, most are held by the containing objects.

Some other constraints of the C API directed some choices in the Rust API.

  • Raw files are no longer Box<> but Rc<> due to the need to retain them for the iterator.2
  • Metadata::String() contain a Vec<u8> instead of a String to allow for the NUL terminated strings in the C API. They are maybe not NUL terminated but ASCII.

New features

Meanwhile I added a few new features:

  • extracting the white balance on most files (saveral Nikon are not yet handled).
  • color converting from the camera space to sRGB on rendering.
  • a multi stage processing pipeline.

This is still not enough to have a complete processing pipeline, but it's a start. It's going towards the only two issue left in the issue tracker. Not that I don't expect more but it's a nice goal post.

Integration

I submitted a PR for Miniaturo to use the Rust version of the library. This is not ready to be merged, but this actually allowed me to fix a few API bits. The new Rust API is relatively close to the old API that was the Rust to C bindings.

1

See issue 13

2

This is likely to change when I make libopenraw multi-thread compatible.

August 27, 2023

GSoC 2023 Final Project Report

Project Title

Make GNOME Platform demos and create offline documentation viewer for Workbench

Mentors

Sonny Piers, Andy Holmes

About The Project

Workbench is an application that lets you experiment and tinker with GNOME technologies. It’s aimed at beginners who want to get into GTK development or developers who want to prototype a feature for their apps.

Workbench comes with a “Library” which is a collection of demos, made to showcase the widgets, APIs, and design patterns of GTK. The demos are made in such a way that they are easy to understand for beginners and highlight only commonly used parts of a widget or an API. The demos can be a good reference for developers who simply want to look up the usage of a widget without going through piles of documentation and directly copy code snippets from the demos.

The main goal of my GSoC project was to create as many demos as possible for Workbench. Since there were a total of 3 students working on the project (2 GSoC and 1 Outreachy), we later decided that our goal was to try and reach 100 demos in the Library. We focused on making demos in GNOME JavaScript and they can be later ported to other languages.

I also worked on creating a Documentation Viewer for Workbench.

The Demos

I made 31 demos for Workbench during the entire GSoC period. The demos cover widgets and APIs from various GNOME libraries — Gtk, Adwaita, Gio, Xdp, and Shumate. I have listed all the demos with links to the PRs and the widget/API they cover below.

I would love to talk about each of these demos but to keep it short, here are some notable ones for me.

Avatar

While this was a very simple demo, it was one of my first contributions. I learned a lot about GTK and Blueprint from this one simple demo. The “Image…” button lets the user select an image locally using GtkFileDialog which was new at the time. It makes use of the Gio._promisfy pattern to present the dialog asynchronously.

Box

GtkBox is a very commonly used widget, it’s a container that arranges its children into a single row or column. It can be hard for beginners to understand or visualize a box, especially with the various alignment properties. We decided to create an interactive demo for this so that users can play around with the widget and learn.

Web View

WebView provides an easy way to load HTML pages and websites in your apps. Making this demo I learned a lot about WebView, and trying to showcase it in a neat way (both in code and UI) was a little challenging but fun.

Animation

AdwAnimation is a simple API that lets you create timed and spring animations. This demo turned out challenging because I had some trouble working with Adw.CallbackAnimationTarget which is used to animate a custom property by updating it using a callback function. I managed to figure it out in the end, and the result was very satisfying.

Map

libshumate is a library that is used to display and render maps. I had never worked with it before so it did take me some time to learn. It was one of the first demos that covered a library outside of org.gnome.Platform.

Actions and Menus

GAction is a very useful API that’s used throughout GTK and GMenu is a way to visually present Actions to the user. Actions can also appear as different types and it was important the demo made the distinction between those. Since Menus and Actions are very closely connected, it was hard to separate those out into two different demos. I brainstormed some ideas with Andy and I am happy with the way the demo turned out. Andy also updated the GJS Guide to talk about Actions and Menus as a way to complement the demo.

Flow Box

GtkFlowBox is a container that arranges its child widgets into a grid, and when the container is resized it’ll readjust the number of columns of the grid. We got a little creative with this demo ;)

Network Monitor

GNetworkMonitor is a simple Gio API that monitors your network status. This demo was quite tricky to make because it was hard to test the demo and we had to find a way to inform the user on how to do that. We came up with the idea of including instructions in the demo and hiding them away in a popover.

After continuous 3 months of effort, I am very happy to say they we hit our combined goal of reaching 100 demos in the Library 🎉

https://medium.com/media/a1016d86527c097764ac39311d897bf9/href

Documentation Viewer

Halfway through GSoC, Sonny gave us an opportunity to take a break from making demos and work on some features for Workbench. I decided to work on providing offline documentation for Workbench. This can be useful because jumping back and forth between Workbench and documentation can be annoying sometimes and there may be times when you don’t have access to a good internet connection.

To get the documentation, it made sense to make use of the SDK that GNOME provides for documentation which is org.gnome.Sdk.Docs. The first iteration of the Documentation Viewer was very simple, it showed all the available namespaces of docs, and clicking on any item on the sidebar loaded the main page of the documentation in a WebView. Since the sidebar was just a flat list for now and only had a few items, we decided to use a ListBox to display the items.

This was a great start but it wasn’t very useful because it was difficult to navigate and browse through the docs.

Add Documentation Viewer by AkshayWarrier · Pull Request #358 · sonnyp/Workbench

Then there were some follow-ups that made minor improvements to the Documentation Viewer, for example, filtering out irrelevant and outdated docs that weren’t needed and including some docs that were initially missing from the sidebar.

After this, I made a massive improvement to the Documentation Viewer in a follow-up PR.

Manuals: Improve design and use TreeExpander by AkshayWarrier · Pull Request #457 · sonnyp/Workbench

  • I ported the Documentation Viewer to GNOME 45 and improved the design by using the new libadwaita 1.4 widgets such as the NavigationSplitView for the sidebar.
  • I switched the ListBox for a ListView and made use of a GtkTreeListModel and GtkTreeExpander to show all the documentation under a given namespace as different sections using collapsible expanders.
  • We also decided to rename the Documentation Viewer as “Manuals”.

Finally, I worked on a basic searching feature inside Manuals to be able to search for specific documentation. While the search is not very good, it’s good enough for now. I’m relying on a GtkFilterListModel to filter out all the docs, it simply checks if the search term is a substring in any of the items or not. I also refactored a lot of code in this PR, to simplify the loading of docs and cleaned up code.

Manuals: Add searching of docs by AkshayWarrier · Pull Request #521 · sonnyp/Workbench

There is still work left to be done for Manuals such as optimizing the loading of documentation, implementing better search, and synchronizing the item selected in the sidebar and the doc shown in the webview. We are also planning to make this into a standalone app in the future.

Acknowledgments

I would like to thank my mentors, Sonny Piers and Andy Holmes, for their constant guidance and support throughout the project.

I would also like to thank GNOME for sponsoring my first overseas trip to attend GUADEC 2023.

August 26, 2023

Acrostic support added to GNOME Crosswords

It has been quite a while since the last update, and we’ve accomplished a lot since then. I’ve also completed finish my GSoC project, which adds Acrostic puzzles support to GNOME Crosswords.

After implementing cell selection for ClueGrid, I added other signals ie. guess and other command actions.

The next step was to finalize the layout and Implement the Navigation as outlined in the design document.

Regarding navigation, it involved two parts. Firstly, when focusing on the main grid, it should use ‘quote_clue’ to progress to the next cell. Secondly, when focusing on the clue grid, it should move to the next cell within the corresponding clue.
Also, we spent a lot of time discussing on how to differentiate the focus between the main and clue grid. We tried different mockups. @bazylevnik0 came up with some good visualization ideas that we applied.

We then spent a whole week resolving bugs, including issues like ‘undo’ not working.

This is how a loaded Acrostic puzzle appears now.

Yay 🎉

Finally, I added some tests to test out my changes.

We’ll be releasing this soon and would greatly appreciate having more Acrostic puzzles. If you’d like to discuss or suggest any ideas, you can join the chat room here.

Related Merge Requests:

  • Add signals to ClueGrid !115
  • Add quote_clue to MainGrid !116
  • Advance to next cell after guessing !118
  • Use quote_clue to advance on MainGrid !119
  • Acrostic Visualization !120
  • CSS improvements !121
  • Fix Undo bug !122 (by jrb)
  • Add tests for acrostic !123

Thanks for reading!

August 25, 2023

One Device to Do it All

In this article I’ll present how I used to organize myself, mainly using my smartphone for that, and why and how I started changing this.

Dependency

On January 1st 2023 at 00:30, my Android smartphone died, and it made me realize how dependent on that device I was. I used it for many aspects of my life: to stay organized, to be informed, to be entertained, or to move around.

Luckily, at that time I was working for Purism and I had a Librem 5, that was the perfect opportunity to try it out in real conditions, as the device I depend on. I started by listing all I used my previous smartphone for, and tried to fulfill the same needs with the Librem 5. It didn’t go well. The device has some great upsides and interesting features, for sure, but the Linux smartphone ecosystem was very far from something I can depend on, especially given how much I was asking from my smartphone.

I ordered a new smartphone in mid-February 2023, and being back on Android after 1½ months not using it helped me discover things I wasn’t expecting.

Infinite Scrolling

Going back on Android after a forced withdrawal period helped me notice how much I was bombarded with notifications from YouTube, from Twitch, from Discord and more. Every time I need my phone to do something productive, they are there to tempt me, right from the lock screen so even looking at the clock isn’t safe.

These notifications are external unsolicited distractions that are at all time close at hand in my pocket or next to me, if not already in my hands given I need my phone for everything. Even without notifications, these distractions are right there, these apps are just a few centimeters away from the app I need to reach.

When I yield to the temptation, I end up on YouTube and its autoplay, on Twitch and its FOMO and raids, on infinitely scrolling social, or on an endless flow of short videos. The distraction is endless and it can be hard to go back to what I initially needed to do.

When I was using the Librem 5, granted I didn’t have access to these applications on my phone, but I was feeling more at peace, I was able to keep my focus on what I am doing more easily, getting things done was easier as I was less disturbed. Using an Android smartphone is like sailing on the Sea of Entertainment, having to be very careful not to fall for the haunting notifications of the sirens, at the risk of drowning in the depths of the Bay of Infinite Scrolling.

Looking for Better Oganization

A smartphone is a full package, so as long as I use one for everything, I can’t bring my calendar and to-do list with me without also bringing YouTube and Twitch along. Because they are on the same device and as I need my productivity tools to always be at hand, distractions are always at hand too. I need to find some way to not bring nagging distractions with me when I want to work, or at the very least I need to keep them away from the tools I need to work.

I decided to reduce my dependency on my smartphone, which implies fulfilling some of its use-cases with tools that don’t subject me to unsolicited distractions. The more I have other ways to do what I use my smartphone for, the less I’m likely to fall into its attention traps. Another benefit would be that if one of my tools fails, there’s only one task I can’t perform anymore. If I depend on my smartphone for everything, it’s a single point of failure and I’ll be screwed when it will inevitably fail.

If I can do things without having to bring my smartphone with me, that’d be perfect. My goal isn’t to get rid of my smartphone, but to stop depending on it for everything, as I currently don’t have the choice to not use it. Having a single device that’s just a thin and light slab that fits in a pocket is extremely comfortable, so if I replace it with many other dedicated tools, I need these to be as small, light, simple, sober, comfortable and to the point as possible. They must do one thing and do it well.

Moreover, distractions and stimulations are important as they help you recharge your batteries and to remain focused longer. I don’t want to remove distracting applications from my smartphone as they are welcome on some occasions, I do need to find ways to rest and to have fun without my smartphone. Distractions should be solicited by me, not soliciting me, and they should be either finite or easy for me to drop out to go back to work.

Planning

While I disliked the idea of depending on a Google service, I was somewhat satisfied with how I kept track of events and tasks on Google Calendar. I thought about using a planner but I never was super satisfied by them, they mostly bring bad memories from high school, but a few months ago I heard of bullet journaling, and it piqued my curiosity.

I decided to replace Google Calendar with a bullet journal. I took some base ideas from the original bullet journal method and tweaked it to better match how I was planning on Google Calendar to make the transition smoother. In practice, I dedicate half a page to the tasks I need to perform during a given week whenever I find the time for it, then I dedicate half a page for each day of the week for tasks I want to perform on a specific day. I rinse and repeat until I reached a month’s worth (so 4 or 5 weeks) and dedicate two pages for tasks that should be performed after the already written weeks, and I only add a month or two at a time.

I’ve been using this method with an 250 pages A5 hardcover notebook since the end of July 2023, it’s a bit early to judge if it worked but so far this new system isn’t much worse than the previous one. The main downside is that having to carry such a large notebook with me at all time is quite inconvenient, and I end up letting it at home when I’m going out, and even at home I tend to forget about it. If I want such a journal to replace my previous planning method, I need to make it easy to carry around at all time, even when I’m at home. I’m considering switching to an 80 pages pocket-sized journal, because I don’t need to bring a year’s worth of planning around with me, I only need to plan a few weeks ahead, and there probably are better methods to plan further ahead.

Another downside I’ve noticed is that I used to plan with time slots in mind, but now my to-dos are tied to a day. I need finer granularity and flexibility, I need to find some way to easily sort when I will perform a task and for how long, and ideally I should lay the tasks down in a graphical way that will help me get a sense of how the day will go. Without that, my days are just a mishmash of to-dos that I end up not doing at all. The good thing about bullet journals is that at heart, they just are DIY planners. I can let my system evolve as I see fit, and if I end up switching to smaller journals I will be able to make them evolve more quickly as each one should last 2 months and not a year.

Knowing the Time

Then I did something that may sound pretty straightforward as I got myself a wristwatch, but it actually wasn’t that simple as I never really liked them. Every time I saw one, either they were large, bulky and heavy, or they had an uncomfortable wristband that just made them annoying to wear, or they were littered with useless features bloating the dial, or they had virtually no notation that could make the time readable. If not all of these at once.

All I wanted is a simple, sober, small and light watch that just showed the time and did it well, which is what I ended up finding with Casio’s MQ-24 series which is priced at around 20€–25€. You can find some really sober and well designed models from Swatch too, though while they seem slightly more polished they will cost you around 60€–75€, which is a non-negligible difference.

I bought a Casio MQ-24-7B2L mid-August 2023, I’ve been using it for a week now and it turns out I like it a lot! To my surprise, I actually like wearing wristwatches, I just needed to find the right model.

Listening to Music

I use my phone to listen to music as I’m walking, biking, or working. While when listening to music on my phone when I’m walking or biking I can’t be distracted as easily by notificiations, that’s still a reason for me to bring it with me, so I’d rather have a dedicated way to listen to music.

As for the wristwatch, I want a simple, sober, small, light and long lasting device that just plays music and do it well. High end devices are large, bulky, expensive, and full of features I don’t care about, so they aren’t what I’m looking for. Low end devices tend to be pretty simple and straightforward, but they all have a display that takes space, add weight, reduces their battery life and are so low quality that you could just as well not have them. So, I started looking for small music players with no display, rapidly leading me to the iPod Shuffle series and devices inspired by it. Such devices have the added advantage of forcing me to not care about choosing what I’m listening to, letting me focus on what I’m doing.

I think my perfect device to listen to music would be similar in form and function to an iPod Shuffle G4, have a good build quality and UX, use mass storage and microSD cards, offer 20 hours of audio playback, feature VoiceOver, feature Bluetooth, use USB C (though using the 3.5mm jack is fine too), have some good color options, and have no internet connection whatsoever. No device matches all of these, and I could be satisfied with less. I found a few devices offering some of these features, but ultimately I settled for a second hand G4 as their build quality is top notch and you can find some for the price of new similar devices of lesser quality. I may have to replace its battery soonish, but they are fairly affordable and the operation doesn’t look too hard.

I’m not fond of having to use some special software to load music into the iPod, but at least there are several FLOSS options. Rhythmbox and Clementine from Flathub didn’t work, the former didn’t recognize the iPod at all and the latter couldn’t use because it wasn’t built with libgpod. Then I’ve tried this Python script which worked just fine and even supported VoiceOver, but didn’t handle the database very cleanly. Later I tried Rhythmbox from Fedora, and it let me easily handle the music on the device is a clean way, with the downside that it doesn’t support VoiceOver.

I filed the device with puzzle game music, contemplative game music, ambient music, jazz, trip-hop, electronic music… anything goes as long as it’s lyrics-free and calm to help me focus. The first two are great as they are designed specifically with focus in mind.

I ended up liking the iPod a lot. Having only purposefully chosen songs on it, coupled with its shuffle feature and it’s great design and build quality make it the perfect tool to help me focus. The music is always adapted to the situation and never distracts me, I don’t have to bother selecting a song, which means I don’t have to touch the device, a device so light and tiny I tend to forget it’s there at all! I like it so much and they are so cheap nowadays (well, if you know where to search) that I actually ordered a second one I’ll fill with energetic music that helps me focus.

Welcome Distractions

Distractions, as long as they are chosen, are not only welcome but necessary to relieve stress and produce dopamine, which help remaining focused.

I used my smartphone a lot to fidget. It may sound ridiculous, but very regularly I spun my smartphone in my hands, and I juggled with it. It turns out playing with something with my hands helps me stay focused, it’s a need I was fulfilling with my smartphone only because it always was at hand. Now that I understand that, I can accept it’s a need and get myself some fidgets before I break a nearly 400€ device. Ideally, I need something small, silent, that can easily be carried around in a pocket, and that can be used with one hand only. I don’t know much about what exists, but it’s pretty easy to find affordable ones on the second hand market.

I used my smartphone to get the news and to study various topics. I could easily fulfill these use cases with newspapers and books when I’m out, and still get news from my usual online sources when I’m home or when bringing my phone with me is fine.

Looking for highly portable ways to be distracted made me remind of my old Game Boy Micro, and I decided to pick it up ad play with it. I forgot how incredibly tiny that device is, and yet it’s still very comfortable to use! Another advantage of Game Boy Advance games is that they are honnest. They don’t show ads, they don’t use psychological strategies to make you pay… they are are self-contained gaming experiences. And because they are designed for a portable system, they allow short play sessions. On top of that, flash cartridges are fairly affordable and allow to bring a few games along in a single cartridge. I don’t play games on smartphones, I don’t play video games at all actually, but I think I will really enjoy taking a habit of taking my Game Boy Micro with me. 😄

Going Further

Thanks to the iPod and the watch, I’ve been able to go out a few times without bringing my smartphone with me, yet without feeling like I was missing something important. I still need my smartphone for communication, for navigation, for its camera, for its torchlight, and much more, but while having it at all with me is a risk, it’s still a lower risk than having it in my pocket. I can still carry the smartphone around in my backpack when I know I’ll need it for something or when I don’t mind risking to be distracted. So don’t think I need to replace it further, and if in the end this isn’t enough and I still get disturbed by my phone, I can just add other tools to replace some of its use cases.

Is this new organization method in search for fewer external distractions a fad, or will it stick? Only time will tell, but so far it’s pretty promising. On a more lighthearted note, now every time I need to leave, instead of having to look for a single tool that’s not too far away because I definitely used it recently, I need to look for several tools scattered all around my apartment, including the smartphone itself. 🙃 Replace “standards” with “tools” and this classic xkcd comic will perfectly describe the situation I’m in.

2023-08-25 Friday

  • Brief Partner call, customer call, admin, partner call.
  • Poked at ChatGPT - perhaps it can help me optimize my code; gave it some RLE code initRow that could benefit from some love; not impressed. Some great abstract tips eg.
    1. Avoid Unnecessary Dynamic Memory Allocation:
    But it came up with a change (complete with comment) that was:
    -    uint32_t scratch[width];
    +    uint32_t *scratch = new uint32_t[width]; // Allocate on the stack
    Not ideal. I was hoping for some clever idea around something new VPCONFLICTD or better some shifts and VPCMPD or somesuch to perhaps make performance better. At least it gave some good factual lookup answers like a prettier technical search engine.
  • Mental note to read Paul Graham on How to do great work - and do better work.
  • Read code, chased a bug collaboratively - fun; filed more tickets.

Fedora meets RHEL: upgrading UBI to RHEL

Six years ago

As part of our efforts to make Fedora Workstation more attractive for developers, particularly those building applications that would be deployed on Red Hat Enterprise Linux, we had made it easy to create gratis, self-supported Red Hat Enterprise Linux virtual machines inside GNOME Boxes. You had to join the Red Hat Developer Program by creating an account on developers.redhat.com and with that you not only had gratis, self-supported access to RHEL, but also a series of other products like Red Hat JBoss Middleware, Red Hat OpenShift Container Platform and so on.

Few years later

Fedora Silverblue became a thing, and so did Toolbx. So, we decided to take this one step further.

Toolbx already had support for Red Hat Enterprise Linux for the past two and a half years. It means that on RHEL hosts, Toolbx sets up a RHEL container that has access to all the software and support that the host is entitled to. On hosts that aren’t running RHEL, if you want, it will set up a gratis, self-supported container based on the Red Hat Universal Base Image, which is a limited subset of RHEL:

$ toolbox create --distro rhel --release 9.2

However

This works well only as long as you are running a Red Hat Enterprise Linux host. Otherwise, you quickly run into the limitations of the Red Hat Universal Base Image, which is designed for distributing server-side applications, and not so much for persistent interactive CLI environments. For example, you won’t enjoy hacking on GNOME components like GTK, Settings, Shell or WebKitGTK in it because of the sheer amount of missing dependencies.

Today is glorious

You can now have gratis, self-supported Red Hat Enterprise Linux Toolbx containers on Fedora hosts that have access to the entire set of RHEL software, beyond the limited subset offered by UBI.

As always, you need to join the Red Hat Developer Program. Make sure that your account has simple content access enabled, by logging into access.redhat.com and then clicking the subscriptions link at the very top left.

Install subscription-manager on your Fedora host to register it with your Red Hat Developer Program account, create a RHEL Toolbx container as before, and you are off to the races!

Thanks to Pino Toscano for helping me iron out all the wrinkles in the pipeline to get everything working smoothly.

Why I picked the biggest furry elephant as my microblogging platform (and refuse to self-host a Mastodon server)

This article will require between 1 and 2 minutes of your attention if you read only the first half; obviously double that if you also feel like reading the second (more philosophical & strategic) half.


As you may know, in addition to this blog here, I have also been microblogging very actively for years (whether on Twitter or on LinkedIn), particularly the day-to-day / work-in-progress of my Free & Open Source software contributions across GNOME and the FreeDesktop, and that habit shall outlive Twitter’s 2022-2023 chaotic hostile takeover and sabotage by its new majority shareholder/owner. I have (reluctantly) found refuge in the shire that is the fediverse, a quirky platform filled with countless technical & usability challenges, but eh, what else have we got left? Tis the last bastion we have (we’ll see what happens when Meta/Facebook “enters the chat”, will it be like what happened with XMPP? 🤷)…

"MAY I JOIN YOU" illustration by David Revoy, CC-by-SA 4.0
MAY I JOIN YOU” by David Revoy − CC-BY-SA 4.0, with fair-use elements

And so, some of you might be pleased to hear that I have been dragged—kicking and screaming—to the Mastodon, as my replacement for the Twitter. I just, uh, “forgot” to tell y’all.

Seriously, it was a really long and busy R&D winter! I didn’t have time to announce this properly here; when registrations reopened and I finally could sign up for the largest* general-purpose instance (maintained by the nonprofit Mastodon gGmbH), it was already “income tax season” and I was busy helping the GNOME Calendar, Nautilus, and Epiphany projects… so I pretty much only had time to change my Twitter profile banner to this picture (because any blog post like what you’re reading is at least 5 to 7 hours of work):

A profile banner on my old social media microblogging account, pointing to my personal Mastodon account

So yeah, follow my personal Mastodon account if you hadn’t found me already (and if you ain’t on Mastodon, I guess there’s still this blog’s email notification system in addition to RSS).

Due to the inherent cultural difference of Mastodon (more on that below), my account there is somewhat more specialized than it was on Twitter. As stated in my introduction toot, I focus mainly on creative, positive & uplifting stuff. It’s more fun, and it pretty much allows me to not worry about the whole “contents warning” minefield. My account is primarily a way for me to share:

  • My findings and contributions to Free & Open Source software “as they happen in the day-to-day”, particularly around GNOME & FreeDesktop-related projects on Linux; I often post my bug reports there, particularly if it is about usability & interaction design, or performance optimization. I sometimes publish awareness and advocacy posts related to such topics.
  • Occasionally, posts related to cycling, ecology, sustainability, gardening, urbanism and societal improvements.

If you’d be interested in things beyond FLOSS and casual life updates, you can also follow my three FLOSS-friendly businesses:

As you can see, those three extra accounts have been very quiet. They are not going to be corporatey spammage / annoyances, they are simply intended to be a way for interested parties to be notified when I have some neat accomplishments to show (whether in FLOSS-related marketing work, or in other industries) or when we publish a new article in those various fields of work. I don’t expect that the Mastodon/fediverse crowd will show much interest in those, but I’ll be flattered if some of you do!


While I kept servicing the Twitter accounts at the same time as the Mastodon accounts for a couple of months, lately it has been abundantly clear that Twitter has truly become a deserted place, and the continued sabotage of its features & reliability has made it pretty much impossible to use professionally and personally.

That’s the end of the article if you simply want to follow my adventures… (◕‿◕✿)

Below are some short philosophical & sustainability observations on the whole situation.


Brief thoughts on the societal bubble of the fediverse

As you can see by my high amount of activity on my personal Mastodon account, I have taken a certain liking to it, even though I was sure I would hate it. I guess this platform sort of works if your audience is primarily FLOSS enthusiasts and you spent months studying the cultural & technical quirks. And yet, I am not blind to the fact that I happen to live in one of the very few bubbles that had high affinity with Mastodon to begin with.

"Follow the white rabbit" scene from Matrix, with the party lady with a white rabbit tattoo

I still miss the ability to do topical research and feel the “pulse of the planet“, and to follow more “mainstream” people like my local policymakers and organizations.

  • Pretty much none of the local architects, urbanists, public institutions, mayors, journalists, businesses, neighbors, and many other “normal” people, are there to engage with. Hoping any of them will join—when even I didn’t want to join unless I had no other options left—is wishful thinking; talking about the fediverse to normies gets you the same blank stares as when you say you run Linux and use FLOSS software—completely abstract to them even if you explain, and they will never care beyond the two-minutes casual conversation. At best they will say “Are you a hacker?!” and then you inevitably say, “Depends on your definition of the word hacker…”
  • There could be a major event occuring in my neighborhood and I would never hear about it in realtime through the fediverse. My neighborhood has over 20 thousand people living there—with higher density than Brooklyn—and yet I know of exactly two other individuals from my neighborhood who are on Mastodon… and of course they work in tech.

Twitter’s global and serendipitous system was the biggest value “for users” (especially journalists) that I feel we have lost, and there is no alternative.

Some thoughts on instances’ sustainability

*: And now, a big footnote for those who wonder why I specifically picked the biggest instance I could, and who might say:

Hipster Ariel says, "But wait… Mastodon.social is too mainstream!"

I do not intend to switch to some niche Mastodon server/instance, unless my life depends on it.

  • There are many technical (and social) reasons why I do not want to be confined to a tiny server where it feels like a Linuxcon happening in a deserted strip mall, and you experience the full extent of decentralization bugs & technical limitations (I have at least two dozen bookmarks to bug reports related to that);
  • I don’t want to lawyer up to figure out which server to pick, and to be subjected to complicated and/or arbitrary rules (FOSStodon users have been learning this lesson in the last few days; I already learned the hard way in 2020 that there was such a thing as picking the “wrong” server or username and realizing that you can’t cleanly change some things)
  • I don’t want to be dependent on a tiny overworked team of volunteers burning out, suddenly disappearing along with the instance, or getting hit by a bus.

Decentralization is very neat conceptually, but I’ll take my chances with the biggest mainstream instance that has the best odds of still being around and able to pay their servers & moderators in two years. Remember, kids, Twitter was not just a failwhale propped up by invisible ropes and ducktape, it was also a white elephant whose value never was in its FUBAR software & infrastructure, but in the brand safety (for advertisers) provided by its paid moderation team, and the handling of legal/moral/safety/security/liability nightmares that user-generated content represent. As I summarized my readings in that tweet, back when tweeting was still a thing:

A couple of years prior, I had also educated various clients about the risks and liabilities of running social media platforms (my clients were, as a result, in a better position to make their own decisions according to their own objectives, threat models, resources and risk tolerance).

I’ve seen enough safety & security nightmares out there throughout the years, so personally speaking, I’ll save myself that hassle, at least; I’m not going to be running my own fediverse instances.

My first conference: GUADEC 2023


In this blog, I'll be sharing my experience attending GUADEC 2023 in person held in Riga, Latvia from 26th July 2023 to 31st July 2023.


24th July 2023

Last month, when I woke up on this date, I was still unsure whether I'd be able to attend GUADEC in person because of the visa process, I was supposed to have my visa received on July 24th, but I wanted to attend GUADEC because it would be my first conference I'd ever attended. So I decided to go to my courier's office to pick up my visa, which was already at a local courier depot. When I opened it, I was overjoyed to see that my visa had been approved. Following this, I began contacting Melissa; she was so supportive that I received my flight tickets and accommodation details the same day, even though she was traveling herself. I flew from Pune to Delhi at midnight, then from Delhi to Helsinki and then from Helsinki to Riga. To be honest, I was also looking forward to the travel experience as well because it was my first international journey. I had a great time taking pictures on the way and arrived in Riga on the evening of July 25th. Melissa if you're reading this thank you again :)


25th July 2023:

So here I was, and I was still amazed that I had made it to GUADEC in such a short time. I was quite thrilled to learn and interact with the other folks here. I was astounded to see such a diverse group of people from various backgrounds coming together to share their knowledge and experience and collaborate. I also met my mentor the very same day I arrived and we discussed his workshop and other details regarding my project.

alt

alt

alt


26th July 2023:

The very next day I attended different talks related to different technologies. There were 2 rooms for 2 different tracks: Dextrum and Magnum. You can find more details here.

alt


27th July 2023:

On the third day, there was a talk about GNOME Javascript, which I was very interested in because it intersected with my interests. Evan Welsh, Philip Chimento, and my mentor Sonny Piers gave this talk, and I was astonished to see my, and Akshay's names on the slide where he presented our work. After the day was over I returned to our hotel in the evening.

alt


28th July 2023:

This was the day that my teammate Akshay and I had to give our project presentation. The entire discussion can be found here. I was also curious about the other projects that the interns were working on, I talked about what their projects were about and tried to learn something from them.

alt

alt


29th July 2023:

Starting today, we had workshops on many different technologies where you could sit and learn while also discussing a certain topic/project.

alt


30th July 2023:

This was the last day of the conference's workshops, and we had another trip scheduled for the next day. But first, we had a dinner reservation.

alt

alt


31st July 2023:

This day, we had a day trip to northern Latvia, where we would tour numerous historical sites before having lunch and returning to Riga in the evening.

alt

alt

alt

alt

alt


After each day ended, I took some time off to explore the city as much as I could before returning to the hotel.

alt

alt

alt

alt


I was amazed to discover how interesting attending a conference could be, and it was a new experience that I loved. It resembled having a family experience away from home in several ways. I will always remember this, and I'm very grateful to GNOME for sponsoring my entire trip and allowing me to attend GUADEC in person. I was able to make a tonne of memories and learn a lot. I was also honored to be presenting my work in front of all the experienced professionals. Looking forward to attending more conferences in the future.

alt

alt

alt


Pitivi GSOC Final Report

Flamegraphs for Sysprof

A long requested feature for Sysprof (and most profiler tools in general) is support for visualizing data as FlameGraphs. They are essentially a different view on the same callgraph data we already generate. So yesterday afternoon I spent a bit of time prototyping them to sneak into GNOME 45.

Many tools out there use the venerable flamegraphs.pl but since we already have all the data conveniently in memory, we just draw it with GtkSnapshot. Colorization comes from the same stacktrace categorization I wrote about previously.

A screenshot of flamegraph visualization of a callgraph in Sysprof.

If you select a new time range using the scrubber at the top, the flamegraph will update to stacktraces limited to that selection.

Selecting frames within the flamegraph will dive into those leaving enough breadcrumbs to work your way back out.

#110 Nailing Down Perfomance Issues

Update on what happened across the GNOME project in the week from August 18 to August 25.

GNOME Core Apps and Libraries

GTK

Cross-platform widget toolkit for creating graphical user interfaces.

Emmanuele Bassi says

Thanks to René de Hesselle from the Inkscape community, GTK has a new macOS CI runner

GNOME Shell

Core system user interface for things like launching apps, switching windows, system search, and more.

Just Perfection announces

GNOME Shell 45 port guide is out. Since GNOME Shell 45 moved to ESM, we want all developers to port their extensions before 45.0 release (September 20th) so they can report to us about the elements needed to be exported (in case they are not exported already).

We also offer our help to all developers on GNOME Discourse and GNOME Extensions Matrix channel: GNOME Matrix Channel IRC Bridge: irc://irc.gimpnet.org/shell-extensions

GNOME Development Tools

Sysprof

A profiling tool that helps in finding the functions in which a program uses most of its time.

hergertme says

Sysprof gained the ability to show you what processes were scheduled per-CPU. You can use this to more effectively nail down performance issues on the desktop.

GNOME Circle Apps and Libraries

Clara Hobbs (she/they) announces

Thanks to the new AdwBreakpoint API in libadwaita 1.4, Chess Clock just merged improved support for larger screen sizes. The clock text is larger when the window is big enough to support it, and portrait mode is enabled whenever the window has a portrait aspect ratio (rather than requiring it to be as narrow as a portrait phone screen). These updates should make the app nicer to use on tablets and laptops.

Workbench

A sandbox to learn and prototype with GNOME technologies.

Sonny reports

Rust support landed in Workbench 🛠️ ❤️ 🦀 Thanks to Julian 🍃 ! We will work on formatter and language server soon.

We are approaching 100 Library demos. Porting them is a great opportunity to learn or work with Rust, specially if you are coming from JavaScript or Vala. Checkout our guide and don’t hesitate to come by #workbench:gnome.org

Third Party Projects

Bilal Elmoussaoui reports

A new release of libmks is out! Adding support for touch events, improving the rendering which would avoid frame drops/incorrect regions updates. Details at https://gitlab.gnome.org/GNOME/libmks/-/releases/0.1.1. I also wrote a blog post recently about my adventures to get damage area propagated from the guest to the host which you can read at https://belmoussaoui.com/blog/16-damage-areas-across-the-virtio-space/

Nokse announces

This week, I’ve released ASCII Draw, an app that lets you draw diagrams, flowcharts, or anything else using nothing but characters. It has many lines styles to choose from.

There are multiple tools available:

  • Straight Lines and Arrows to connect points on your graphs
  • Rectangle to construct outlines effortlessly
  • Freehand Lines to draws connected lines following your mouse movement
  • Text to easily preview and write text on your canvas
  • Eraser, Picker, Filled Rectangle and Freehand Brush

Parabolic

Download web video and audio.

Nick says

Parabolic V2023.8.3 is here!

This release includes some new feature to make Parabolic even more configurable. Read about them below :)

Here’s the changelog:

  • Added a new advanced download option to split chapters
  • Added a new preference to enable SponsorBlock for YouTube downloads
  • Updated translations (Thanks everyone on Weblate!)

Denaro

Manage your personal finances.

Nick says

Denaro V2023.8.1 is here!

This release includes some new features and fixes that you can read about below :)

Here’s the changelog:

  • Added a password strength indicator when creating an account password
  • Added an Amount Display Style option to custom currency settings
  • Fixed an issue where selecting the current month on an empty account in the calendar would cause a crash
  • Fixed an issue where adding receipts to a new transaction would cause a crash
  • Improved UI/UX
  • Updated translations (Thanks to everyone on Weblate)!

Miscellaneous

Sophie (she/her) announces

GNOME 45 is reaching its next step in release preparation. After three weeks of UI freeze, on Saturday, Aug 26, GNOME 45 will also enter string freeze. After 23:59 UTC that day, all string changes in Core components will require approval from the i18n team.

GNOME Foundation

Caroline Henriksen announces

With GUADEC 2023 completed Foundation staff have been busy wrapping up final conference tasks while moving forward with planning GNOME.Asia 2023, LAS 2024, and GUADEC 2024. Some of these tasks include collecting feedback from our post-event surveys, sorting through conference photos, and providing updates to our event sponsors. We’re currently working on finding the best solution for sharing the conference photos with everyone and will provide more updates on that soon! We’re also looking for community feedback on how we structure GUADEC 2024 talk and BoF days. Let us know if you would prefer to attend GUADEC talks on weekdays or over the weekend by answering our poll here.

Outside of events, we’ve been wrapping up the 2021-2022 Annual Report which is now available on the Foundation website, and getting started on a new video for the upcoming GNOME 45 release.

Reminder: The GNOME.Asia 2023 Call for Participation is open! If you would like to submit a talk to this year’s summit please read our news post for more details. All applications are due by September 1, 2023.

Volunteer Opportunity: We’re live-streaming the GUADEC talks, but are looking for a volunteer to edit the final footage into individual talk videos. If that sounds like a project you’re interested in let us know by emailing chenriksen@gnome.org.

feborges reports

The GNOME Foundation is looking for Mentors and projects for the Outreachy December-March internships. If you are interested in mentoring, please visit https://discourse.gnome.org/t/deadline-sept-20-2023-call-for-mentors-for-outreachy-december-23-march-24-cohort/16748 for more info.

That’s all for this week!

See you next week, and be sure to stop by #thisweek:gnome.org with updates on your own projects!

August 24, 2023

2023-08-24 Thursday

  • Technical planning call, perf. call, COOL community meeting, a round-up of our Hack-week, catch-up with Miklos and a break from calls to do administration until late at night.

August 23, 2023

The Mirror

The GObject type system has been serving the GNOME community for more than 20 years. We have based an entire application development platform on top of the features it provides, and the rules that it enforces; we have integrated multiple programming languages on top of that, and in doing so, we expanded the scope of the GNOME platform in a myriad of directions. Unlike GTK, the GObject API hasn’t seen any major change since its introduction: aside from deprecations and little new functionality, the API is exactly the same today as it was when GLib 2.0 was released in March 2002. If you transported a GNOME developer from 2003 to 2023, they would have no problem understanding a newly written GObject class; though, they would likely appreciate the levels of boilerplate reduction, and the performance improvements that have been introduced over the years.

While having a stable API last this long is definitely a positive, it also imposes a burden on maintainers and users, because any change has to be weighted against the possibility of introducing unintended regressions in code that uses undefined, or undocumented, behaviour. There’s a lot of leeway when it comes to playing games with C, and GObject has dark corners everywhere.

The other burden is that any major change to a foundational library like GObject cascades across the entire platform. Releasing GLib 3.0 today would necessitate breaking API in the entirety of the GNOME stack and further beyond; it would require either a hard to execute “flag day”, or an impossibly long transition, reverberating across downstreams for years to come. Both solutions imply amounts of work that are simply not compatible with a volunteer-based project and ecosystem, especially the current one where volunteers of core components are now stretched thin across too many projects.

And yet, we are now at a cross-roads: our foundational code base has reached the point where recruiting new resources capable of affecting change on the project has become increasingly difficult; where any attempt at performance improvement is heavily counterbalanced by the high possibility of introducing world-breaking regressions; and where fixing the safety and ergonomics of idiomatic code requires unspooling twenty years of limitations inherent to the current design.

Something must be done if we want to improve the coding practices, the performance, and the safety of the platform without a complete rewrite.

The Mirror

‘Many things I can command the Mirror to reveal,’ she answered, ‘and to some I can show what they desire to see. But the Mirror also show things unbidden, and those are often stranger and more profitable than things we wish to behold. What you will see, if you leave the Mirror free to work, I cannot tell. For it shows things that were, and things that are, and things that yet may be. But which it is that he sees, even the wisest cannot always tell. Do you wish to look?’ — Lady Galadriel, “The Lords of the Rings”, Volume 1: The Fellowship of the Ring, Book 2: The Ring Goes South

In order to properly understand what we want to achieve, we need to understand the problem space that the type system is meant to solve, and the constraints upon which the type system was implemented. We do that by holding GObject up to Galadriel’s Mirror, and gazing into its surface.

Things that were

History became legend. Legend became myth. — Lady Galadriel, “The Lord of the Rings: The Fellowship of the Ring”

Before GObject there was GtkObject. It was a simpler time, it was a simpler stack. You added types only for the widgets and objects that related to the UI toolkit, and everything else was C89, with a touch of undefined behaviour, like calling function pointers with any number of arguments. Properties were “arguments”, likes were florps, and the timeline went sideways.

We had a class initialisation and an instance initialisation functions; properties were stored in a global hash table, but the property multiplexer pair of functions was stored on the type data instead of using the class structure. Types did not have private data: you only had keyed fields. No interfaces, only single inheritance. GtkObject was reference counted, and had an initially “floating” reference, to allow transparent ownership transfer from child to parent container when writing C code, and make the life of every language binding maintainer miserable in the process. There were weak references attached to an instance that worked by invoking a callback when the instance’s reference count reached zero. Signals operated exactly as they do today: large hash table of signal information, indexed by an integer.

None of this was thread safe. After all, GTK was not thread safe either, because X11 was not thread safe; and we’re talking about 1997: who even had hardware capable of multi-threading at the time? NPTL wasn’t even a thing, yet.

The introduction of GObject in 2001 changed some of the rules—mainly, around the idea of having dynamic types that could be loaded and unloaded in order to implement plugins. The basic design of the type system, after all, came from Beast, a plugin-heavy audio application, and it was extended to subsume the (mostly) static use cases of GTK. In order to support unloading, the class aspect of the type system was allowed to be cleaned up, but the type data had to be registered and never unloaded; in other words, once a type was registered, it was there forever.

Arguments” were renamed to properties, and were extended to include more than basic types, provide validations, and notify of changes; the overall design was still using a global hash table to store all the properties across all types. Properties were tied to the GObject type, but the property definition existed as a separate type hierarchy that was designed to validate values, but not manage fields inside a class. Signals were ported wholesale, with minimal changes mainly around the marshalling of values and abstracting closures.

The entire plan was to have GObject as one of the base classes at the root of a specific hierarchy, with all the required functionality for GTK to inherit from for its own GtkObject, while leaving open the possibility of creating other hierarchies, or even other roots with different functionality, for more lightweight objects.

These constraints were entirely intentional; the idea was to be able to port GTK to the new type system, and to an out of tree GLib, during the 1.3 development phase, and minimise the amount of changes necessary to make the transition work not just inside GTK, but inside of GNOME too.

Little by little, the entire GObject layer was ported towards thread safety in the only way that worked without breaking the type system: add global locks around everything; use read-write locks for the type data; lock the access and traversal of the property hash table and of the signals table. The only real world code bases that actively exercised multi-threading support were GStreamer and the GNOME VFS API that was mainly used by Nautilus.

With the 3.0 API, GTK dropped the GtkObject base type: the whole floating reference mechanism was moved to GObject, and a new type was introduced to provide the “initial” floating reference to derived types. Around the same time, a thread-safe version of weak references for GObject appeared as a separate API, which confused the matter even more.

Things that are

Darkness crept back into the forests of the world. Rumour grew of a Shadow in the East, whispers of a Nameless Fear. — Lady Galadriel, “The Lord of the Rings: The Fellowship of the Ring”

Let’s address the elephant in the room: it’s completely immaterial how many lines of code you have to deal with when creating a new type. It’s a one-off cost, and for most cases, it’s a matter of using the existing macros. The declaration and definition macros have the advantages of enforcing a series of best practices, and keep the code consistent across multiple projects. If you don’t want to deal with boilerplate when using C, you chose the wrong language to begin with. The existence of excessive API is mainly a requirement to allow other languages to integrate their type system with GObject’s own.

The dynamic part of the type system has gotten progressively less relevant. Yes: you can still create plugins, and those can register types; but classes are never unloaded, just like their type data. There is some attempt at enforcing an order of operations: you cannot just add an interface after a type has been instantiated any more; and while you can add properties and signals after class initialisation, it’s mainly a functionality reserved for specific language bindings to maintain backward compatibility.

Yes, defining properties is boring, and could probably be simplified, but the real cost is not in defining and installing a GParamSpec: it’s in writing the set and get property multiplexers, validating the values, boxing and unboxing the data, and dealing with the different property flags; none of those things can be wrapped in some fancy C preprocessor macros—unless you go into the weeds with X macros. The other, big cost of properties is their storage inside a separate, global, lock-happy hash table. The main use case of this functionality—adding entirely separate classes of properties with the same semantics as GObject properties, like style properties and child properties in GTK—has completely fallen out of favour, and for good reasons: it cannot be managed by generic code; it cannot be handled by documentation generators without prior knowledge; and, for the same reason, it cannot be introspected. Even calling these “properties” is kind of a misnomer: they are value validation objects that operate only when using the generic (and less performant) GObject accessor API, something that is constrained to things like UI definition files in GTK, or language bindings. If you use the C accessors for your own GObject type, you’ll have to implement validation yourself; and since idiomatic code will have the generic GObject accessors call the public C API of your type, you get twice the amount of validation for no reason whatsoever.

Signals have mostly been left alone, outside of performance improvements that were hard to achieve within the constraints of the existing implementation; the generic FFI-based closure turned out to be a net performance loss, and we’re trying to walk it back even for D-Bus, which was the main driver for it to land in the first place. Marshallers are now generated with a variadic arguments variant, to reduce the amount of boxing and unboxing of GValue containers. Still, there’s not much left to squeeze out of the old GSignal API.

The atomic nature of the reference counting can be a costly feature, especially for code bases that are by necessity single-threaded; the fact that the reference count field is part of the (somewhat) public API prevents fundamental refactorings, like switching to biased reference counting for faster operations on the same thread that created an instance. The lack of room on GObject also prevents storing the thread ID that owns the instance, which in turn prevents calling the GObjectClass.dispose() and GObjectClass.finalize() virtual functions on the right thread, and requires scheduling the destruction of an object on a separate main context, or locking the contents of an object at a further cost.

Things that yet may be

The quest stands upon the edge of a knife: stray but a little, and it will fail to the ruin of all. Yet hope remains, while the company is true. — Lady Galadriel, “The Lord of the Rings: The Fellowship of the Ring”

Over the years, we have been strictly focusing on GObject: speeding up its internals, figuring out ways to improve property registration and performance, adding new API and features to ensure it behaved more reliably. The type system has also been improved, mainly to streamline its use in idiomatic GObject code bases. Not everything worked: properties are still a problem; weak references and pointers are a mess, with two different API that interact badly with GObject; signals still exists on a completely separate plane; GObject is still wildly inefficient when it comes to locking.

The thesis of this strawman is that we reached the limits of backwards compatibility of GObject, and any attempt at improving it will inevitably lead to a more brittle code, rife with potential regressions. The typical answer, in this case, would be to bump the API/ABI of GObject, remove the mistakes of the past, and provide a new idiomatic approach. Sadly, doing so not only would require a level of resources we, as the GLib project stewards, cannot provide, but it would also completely break the entire ecosystem in a way that is not recoverable. Either nobody would port to the new GObject-3.0 API; or the various projects that depend on GObject would inevitably fracture, following whichever API version they can commit to; in the meantime, downstream distributors would suffer the worst effects of the shared platform we call “Linux”.

Between inaction and slow death, and action with catastrophic consequences, there’s the possibility of a third option: what if we stopped trying to emulate Java, and have a single “god” type?

Our type system is flexible enough to support partitioning various responsibilities, and we can defer complexity where it belongs: into faster moving dependencies, that have the benefit of being able to iterate and change at a much higher rate than the foundational library of the platform. What’s the point of shoving every possible feature into the base class, in order to cover ever increasingly complex use cases across multiple languages, when we can let consumers decide to opt into their own well-defined behaviours? What GObject ought to provide is a set of reliable types that can be combined in expressive ways, and that can be inspected by generic API.

A new, old base type

We already have a derivable type, called GTypeInstance. Typed instances don’t have any memory management: once instantiated, they can only be moved, or freed. All our objects already are typed instances, since GObject inherits from it. Contrary to the current common practices we should move towards using GTypeInstance for our types.

There’s a distinct lack of convenience API for defining typed instances, mostly derived from the fact that GTypeInstance is seen as a form of “escape hatch” for projects to use in order to avoid GObject. In practice, there’s nothing that prevents us from improving the convenience of creating new instantiatable/derivable types, especially if we start using them more often. The verbose API must still exist, to allow language bindings and introspection to handle this kind of types, but just like we made convenience macros for declaring and defining GObject types, we can provide macros for new typed instances, and for setting up a GValue table.

Optional functionality

Typed instances require a wrapper API to free their contents before calling g_type_free_instance(). Nothing prevents us from adding a GFinalizable interface that can be implemented by a GTypeInstance, though: interfaces exist at the type system level, and do not require GObject to work.

typedef struct {
  void (* finalize) (GFinalizable *self);
} GFinalizableInterface;

If a typed instance provides an implementation of GFinalizable, then g_type_free_instance() can free the contents of the instance by calling g_finalizable_finalize().

This interface is optional, in case your typed instance just contains simple values, like:

typedef struct {
  GTypeInstance parent;

  bool is_valid;
  double x1, y1;
  double x2, y2;
} Box;

and does not require deallocations outside of the instance block.

A similar interface can be introduced for cloning instances, allowing a copy operation alongside a move:

typedef struct {
  GClonable * (* clone) (GClonable *self);
} GClonable;

We could then introduce g_type_instance_clone() as a generic entry point that either used GClonable, or simply allocated a new instance and called memcpy() on it, using the size of the instance (and eventual private data) known to the type system.

The prior art for this kind of functionality exists in GIO, in the form of the GInitable and GAsyncInitable interfaces; unfortunately, those interfaces require GObject, and they depend on GCancellable and GAsyncResult objects, which prevent us from moving them into the lower level API.

Typed containers and life time management

The main functionality provided by GObject is garbage collection through reference counting: you acquire a (strong) reference when you need to access an instance, and release it when you don’t need the instance any more. If the reference you released was the last one, the instance gets finalized.

Of course, once you introduce strong references you open the door to a veritable bestiary of other type of references:

  • weak references, used to keep a “pointer” to the instance, and get a notification when the last reference drops
  • floating references, used as a C convenience to allow ownership transfer of newly constructed “child” objects to their “parent”
  • toggle references, used by language bindings that acquire a strong reference on an instance they wrap with a native object; when the toggle reference gets triggered it means that the last reference being held is the one on the native wrapper, and the wrapper can be dropped causing the instance to be finalized

All of these types of reference exist inside GObject, but since they were introduced over the years, they are bolted on top of the base class using the keyed data storage, which comes with its own costly locking and ordering; they are also managed through the finalisation code, which means there are re-entrancy issues or undefined ordering behaviours that routinely crop up over the years, especially when trying to optimise construction and destruction phases.

None of this complexity is, strictly speaking, necessary; we don’t care about an instance being reference counted: a “parent” object can move the memory of a “child” typed instance directly into its own code. What we care about is that, whenever other code interacts with ours, we can hand out a reference to that memory, so that ownership is maintained.

Other languages and standard libraries have the same concept:

These constructs are not part of a base class: they are wrappers around instances. This means you’re not handing out a reference to an instance: you are handing out a reference to a container, which holds the instance for you. The behaviour of the value is made explicit by the type system, not implicit to the type.

A simple implementation of a typed “reference counted” container would provide us with both strong and weak references:

typedef struct _GRc GRc;
typedef struct _GWeak GWeak;

GRc *g_rc_new (GType data_type, gpointer data);

GRc *g_rc_acquire (GRc *rc);
void g_rc_release (GRc *rc);

gpointer g_rc_get_data (GRc *rc);

GWeak *g_rc_downgrade (GRc *rc);
GRc *g_weak_upgrade (GWeak *weak);

bool g_weak_is_empty (GWeak *weak);
gpointer g_weak_get_data (GWeak *weak);

Alongside this type of containers, we could also have a specialisation for atomic reference counted containers; or pinned containers, which guarantee that an object is kept in the same memory location; or re-implement referenced containers inside each language binding, to ensure that the behaviour is tailored to the memory management of those languages.

Specialised types

Container types introduce the requirement of having the type system understand that an object can be the product of two types: the type of the container, and the type of the data. In order to allow properties, signals, and values to effectively provide introspection of this kind of container types we are going to need to introduce “specialised” types:

  • GRc exists as a “generic”, abstract type in the type system
  • any instance of GRc that contains a instance of type A gets a new type in the type system

A basic implementation would look like:

GRc *
g_rc_new (GType data_type, gpointer data)
{
  // Returns an existing GType if something else already
  // has registered the same GRc<T>
  GType rc_type =
    g_generic_type_register_static (G_TYPE_RC, data_type);

  // Instantiates GRc, but gives it the type of
  // GRc<T>; there is only the base GRc class
  // and instance initialization functions, as
  // GRc<T> is not a pure derived type
  GRc *res = (GRc *) g_type_create_instance (rc_type);
  res->data = data;

  return res;
}

Any instance of type GRc<A> satisfies the “is-a” relationship with GRc, but it is not a purely derived type:

GType rc_type =
  ((GTypeInstance *) rc)->g_class.g_type;
g_assert_true (g_type_is_a (rc_type, G_TYPE_RC));

The GRc<A> type does not have a different instance or class size, or its own class and instance initialisation functions; it’s still an instance of the GRc type, with a different GType. The GRc<A> type only exists at run time, as it is the result of the type instantiation; you cannot instantiate a plain GRc, or derive your type from GRc in order to create your own reference counted type, either:

// WRONG
GRc *rc = g_type_create_instance (G_TYPE_RC);

// WRONG
typedef GRc GtkWidget;

You can only use a GRc inside your own instance:

typedef struct {
  // GRc<GtkWidget>
  GRc *parent;
  // GRc<GtkWidget>
  GRc *first_child;
  // GRc<GtkWidget>
  GRc *next_sibling;

  // ...
} GtkWidgetPrivate;

Tuple types

Tuples are generic containers of N values, but right now we don’t have any way of formally declaring them into the type system. A hack is to use arrays of similarly typed values, but with the deprecation of GValueArray—which is a bad type that does not allow reference counting, and does not give you guarantees anyway—we only have C arrays and pointer types.

Registering a new tuple type would work like a generic type: a base GTuple abstract type as the “parent”, and a number of types:

typedef struct _GTuple GTuple;

GTuple *
g_tuple_new_int (size_t n_elements,
                 int elements[])
{
  GType tuple_type =
    g_tuple_type_register_static (G_TYPE_TUPLE, n_elements, G_TYPE_INT);

  GTuple *res = g_type_create_instance (tuple_type);
  for (size_t i = 0; i < n_elements; i++)
    g_tuple_add (res, elements[i]);

  return res;
}

We can also create specialised tuple types, like pairs:

typedef struct _GPair GPair;

GPair *
g_pair_new (GType this_type,
            GType that_type,
            ...);

This would give use the ability to standardise our API around fundamental types, and reduce the amount of ad hoc container types that libraries have to define and bindings have to wrap with native constructs.

Sum types

Of course, once we start with specialised types, we end up with sum types:

typedef enum {
  SQUARE,
  RECT,
  CIRCLE,
} ShapeKind;

typedef struct {
  GTypeInstance parent;

  ShapeKind kind;

  union {
    struct { Point origin; float side; };
    struct { Point origin; Size size; };
    struct { Point center; float radius; };
  } shape;
} Shape;

As of right now, discriminated unions don’t have any special handling in the type system: they are generally boxed types, or typed instances, but they require type-specific API to deal with the discriminator field and type. Since we have types for enumerations and instances, we can register them at the same time, and provide offsets for direct access:

GType
g_sum_type_register_static (const char *name,
                            size_t class_size,
                            size_t instance_size,
                            GType tag_enum_type,
                            offset_t tag_field);

This way it’s possible to ask the type system for:

  • the offset of the tag in an instance, for direct access
  • all the possible values of the tag, by inspecting its GEnum type

From then on, we can easily build types like Option and Result:

typedef enum {
  G_RESULT_OK,
  G_RESULT_ERR
} GResultKind;

typedef struct {
  GTypeInstance parent;

  GResultKind type;
  union {
    GValue value;
    GError *error;
  } result;
} GResult;

// ...
g_sum_type_register_static ("GResult",
                            sizeof (GResultClass),
                            sizeof (GResult),
                            G_TYPE_RESULT_KIND,
                            offsetof (GResult, type));

// ...
GResult *
g_result_new_boolean (gboolean value)
{
  GType res_type =
    g_generic_type_register_static (G_TYPE_RESULT,
                                    G_TYPE_BOOLEAN)
  GResult *res =
    g_type_create_instance (res_type);
  g_value_set_boolean (&res->result.value, value);

  return res;
}

// ...
g_autoptr (GResult) result = obj_finish (task);
switch (g_result_get_kind (result)) {
  case G_RESULT_OK:
    g_print ("Result: %s\n",
      g_result_get_boolean (result)
        ? "true"
        : "false");
    break;

  case G_RESULT_ERROR:
    g_printerr ("Error: %s\n",
      g_result_get_error_message (result));
    break;
}

// ...
g_autoptr (GResult) result =
  g_input_stream_read_bytes (stream);
if (g_result_is_error (result)) {
  // ...
} else {
  g_autoptr (GBytes) data = g_result_get_boxed (result);
  // ...
}

Consolidating GLib and GType

Having the type system in a separate shared library did make sense back when GLib was spun off from GTK; after all, GLib was mainly a set of convenient data types for a language that lacked a decent standard library. Additionally, not many C projects were interested in the type system, as it was perceived as a big chunk of functionality in an era where space was at a premium. These days, the smallest environment capable of running GLib code is plenty capable of running the GObject type system as well. The separation between GLib data types and the GObject type system has created data types that are not type safe, and work by copying data, by having run time defined destructor functions, or by storing pointers and assuming everything will be fine. This leads to code duplication between shared libraries, and prevents the use of GLib data types in the public API, lest the introspection information gets lost.

Moving the type system inside GLib would allow us to have properly typed generic container types, like a GVector replacing GArray, GPtrArray, GByteArray, as well as the deprecated GValueArray; or a GMap and a GSet, replacing GHashTable, GSequence, and GtkRBTree. Even the various list models could be assembled on top of these new types, and moved out of GTK.

Current consumers of GLib-only API would still have their basic C types, but if they don’t want to link against a slightly bigger shared library that includes GTypeInstance, GTypeInterface, and the newly added generic, tuple, and sum types, then they would probably be better served by projects like c-util instead.

Properties

Instead of bolting properties on top of GParamSpec, we can move their definition into the type system; after all, properties are a fundamental part of a type, so it does not make sense to bind them to the class instantiation. This would also remove the long-standing issue of properties being available for registration long after a class has been initialised; it would give us the chance to ship a utility for inspecting the type system to get all the meta-information on the hierarchy and generating introspection XML without having to compile a small binary.

If we move property registration to the type registration we can also finally move away from multiplexed accessors, and use direct instance field access where applicable:

GPropertyBuilder builder;

g_property_builder_init (&builder,
  G_TYPE_STRING, "name");
// Stop using flags, and use proper setters; since
// there's no use case for unsetting the readability
// flag, we don't even need a boolean argument
g_property_builder_set_readwrite (&builder);
// The offset is used for read and write access...
g_property_builder_set_private_offset (&builder,
  offsetof (GtkWidgetPrivate, name));
// ... unless an accessor function is provided; in
// this case we want setting a property to go through
// a function
g_property_builder_set_setter_func (&builder,
  gtk_widget_set_name);

// Register the property into the type; we return the
// offset of the property into the type node, so we can
// access the property definition with a fast look up
properties[NAME] =
  g_type_add_instance_property (type,
    g_property_builder_end (&builder));

Accessing the property information would then be a case of looking into the type system under a single reader lock, instead of traversing all properties in a glorified globally locked hash table.

Once we have a property registered in the type system, accessing it is a matter of calling API on the GProperty object:

void
gtk_widget_set_name (GtkWidget *widget,
                     const char *name)
{
  GProperty *prop =
    g_type_get_instance_property (GTK_TYPE_WIDGET,
                                  properties[NAME]);

  g_property_set (prop, name);
}

Signals

Moving signal registration into the type system would allow us to subsume the global locking into the type locks; it would also give us the chance to simplify some of the complexity for re-emission and hooks:

GSignalBuilder builder;

g_signal_builder_init (&builder, "insert-text");
g_signal_builder_set_args (&builder, 3,
  (GSignalArg[]) {
    { .name = "text", .gtype = G_TYPE_STRING },
    { .name = "length", .gtype = G_TYPE_SIZE },
    { .name = "position", .gtype = G_TYPE_OFFSET },
  });
g_signal_builder_set_retval (&builder,
  G_TYPE_OFFSET);
g_signal_builder_set_class_offset (&builder,
  offsetof (EditableClass, insert_text));

signals[INSERT_TEXT] =
  g_type_add_class_signal (type,
    g_signal_builder_end (&builder));

By taking the chance of moving signals out of the their own namespace we can also move to a model where each class is responsible for providing the API necessary to connect and emit signals, as well as providing callback types for each signal. This would allow us to increase type safety, and reduce the reliance on generic API:

typedef offset_t (* EditableInsertText) (Editable *self,
                                         const char *text,
                                         size_t length,
                                         offset_t position);

unsigned long
editable_connect_insert_text (Editable *self,
                              EditableInsertText callback,
                              gpointer user_data,
                              GSignalFlags flags);

offset_t
editable_emit_insert_text (Editable *self,
                           const char *text,
                           size_t length,
                           offset_t position);

Extending the type system

Some of the metadata necessary to provide properly typed properties and signals is missing from the type system. For instance, by design, there is no type representing a uint16_t; we are supposed to create a GParamSpec to validate the value of a G_TYPE_INT in order to fit in the 16bit range. Of course, this leads to excessive run time validation, and relies on C’s own promotion rules for variadic arguments; it also does not work for signals, as those do not use GParamSpec. More importantly, though, the missing connection between C types and GTypes prevents gathering proper introspection information for properties and signal arguments: if we only have the GType we cannot generate the full metadata that can be used by documentation and language bindings, unless we’re willing to lose specificity.

Not only the type system should be sufficient to contain all the standard C types that are now available, we also need the type system to provide us with enough information to be able to serialise those types into the introspection data, if we want to be able to generate code like signal API, type safe bindings, or accurate documentation for properties and signal handlers.

Introspection

Introspection exists outside of GObject mainly because of dependencies; the parser, abstract syntax tree, and transformers are written in Python and interface with a low level C tokeniser. Adding a CPython dependency to GObject is too much of a stretch, especially when it comes to bootstrapping a system. While we could keep the dependency optional, and allow building GObject without support for introspection, keeping the code separate is a simpler solution.

Nevertheless, GObject should not ignore introspection. The current reflection API inside GObject should generate data that is compatible with the libgirepository API and with its GIR parser. Currently, gobject-introspection is tasked with generating a small C executable, compiling it, running it to extract metadata from the type system, as well as the properties and signals of a GObject type, and generate XML that can be parsed and included into the larger GIR metadata for the rest of the ABI being introspected. GObject should ship a pre-built binary, instead; it should dlopen the given library or executable, extract all the type information, and emit the introspection data. This would not make gobject-introspection more cross-compilable, but it would simplify its internals and its distributability. We would not need to know how to compile and run C code from a Python script, for one; a simple executable wrapper around a native copy of the GObject-provided binary would be enough.

Ideally, we could move the girepository API into GObject itself, and allow it to load the binary data compiled out of the XML; language bindings loading the data at run time would then need to depend on GObject instead of an additional library, and we could ship the GIR → typelib compiler directly with GLib, leaving gobject-introspection to deal only with the parsing of C headers, docblocks, and annotations, to generate the XML representation of the C/GObject ABI.

There and back again

And the ship went out into the High Sea and passed on into the West, until at last on a night of rain Frodo smelled a sweet fragrance on the air and heard the sound of singing that came over the water. And then it seemed to him that as in his dream in the house of Bombadil, the grey rain-curtain turned all to silver glass and was rolled back, and he beheld white shores and beyond them a far green country under a swift sunrise. — “The Lord of the Rings”, Volume 3: The Return of the King, Book 6: The End of the Third Age

The hard part of changing a project in a backward compatible way is resisting the temptation of fixing the existing design. Some times it’s necessary to backtrack the chain of decisions, and consider the extant code base a dead branch; not because the code is wrong, or bug free, but because any attempt at doubling down on the same design will inevitably lead to breakage. In this sense, it’s easy to just declare “maintenance bankruptcy”, and start from a new major API version: breaks allow us to fix the implementation, at the cost of adapting to new API. For instance, widgets are still the core of GTK, even after 4 major revisions; we did not rename them to “elements” or “actors”, and we did not change how the windows are structured. You are still supposed to build a tree of widgets, connect callbacks to signals, and let the main event loop run. Porting has been painful because of underlying changes in the graphics stack, or because of portability concerns, but even with the direction change of favouring composition over inheritance, the knowledge on how to use GTK has been transferred from GTK 1 to 4.

We cannot do the same for GObject. Changing how it is implemented implies changing everything that depends on it; it means introducing behavioural changes in subtle, and hard to predict ways. Luckily for us, the underlying type system is still flexible and nimble enough that it can give us the ability to change direction, and implement an entirely different approach to object orientation—one that is more in line with languages like modern C++ and Rust. By following new approaches we can slowly migrate our platform to other languages over time, with a smaller impedance mismatch caused by the current design of our object model. Additionally, by keeping the root of the type system, we maintain the ability to provide a stable C ABI that can be consumed by multiple languages, which is the strong selling point of the GNOME ecosystem.

Why do all of this work, though? Compared to a full API break, this proposal has the advantage of being tractable and realistic; I cannot overemphasise enough how little appetite there is for a “GObject 3.0” in the ecosystem. The recent API bump from libsoup2 to libsoup3 has clearly identified that changes deep into the stack end up being too costly an effort: some projects have found it easier to switch to another HTTP library altogether, rather than support two versions of libsoup for a while; other projects have decided to drop compatibility with libsoup2, forcing the hand of every reverse dependency both upstream and downstream. Breaking GObject would end up breaking the ecosystem, with the hope of a “perfect” implementation way down the line and with very few users on one side, and a dead branch used by everybody else on the other.

Of course, the complexity of the change is not going to be trivial, and it will impact things like the introspection metadata and the various language bindings that exist today; some bindings may even require a complete redesign. Nevertheless, by implementing this new object model and leaving GObject alone, we buy ourselves enough time and space to port our software development platform towards a different future.

Maybe this way we will get to save the Shire; and even if we give up some things, or even lose them, we still get to keep what matters.

Retirement

Apparently it’s nearly four years since I last posted to my blog. Which is, to a degree, the point here. My time, and priorities, have changed over the years. And this lead me to the decision that my available time and priorities in 2023 aren’t compatible with being a Debian or Ubuntu developer, and realistically, haven’t been for years. As of earlier this month, I quit as a Debian Developer and Ubuntu MOTU.

I think a lot of my blogging energy got absorbed by social media over the last decade, but with the collapse of Twitter and Reddit due to mismanagement, I’m trying to allocate more time for blog-based things instead. I may write up some of the things I’ve achieved at work (.NET 8 is now snapped for release Soon™). I might even blog about work-adjacent controversial topics, like my changed feelings about the entire concept of distribution packages. But there’s time for that later. Maybe.

I’ll keep tagging vaguely FOSS related topics with the Debian and Ubuntu tags, which cause them to be aggregated in the Planet Debian/Ubuntu feeds (RSS, remember that from the before times?!) until an admin on those sites gets annoyed at the off-topic posting of an emeritus dev and deletes them.

But that’s where we are. Rather than ignore my distro obligations, I’ve admitted that I just don’t have the energy any more. Let someone less perpetually exhausted than me take over. And if they don’t, maybe that’s OK too.

Call for Mentors and projects for Outreachy December ’23 – March ’24 cohort

The GNOME Foundation is interested in sponsoring up to 3 Outreachy projects for the December-March cohort.

If you are interested in mentoring AND have a project idea in mind, visit GNOME: Call for Outreachy mentors and volunteer and submit your proposal.

We can also use pre-submitted ideas from our Internship project ideas repositor .

GNOME has a committee (Allan Day, Matthias Clasen and Sri Ramkrishna) that will triage projects before approval. More information about GNOME’s participation in Outreachy is available at Outreach/Outreachy – GNOME Wiki! .

If you have any questions, please feel free to reply to this thread or e-mail soc-admins@gnome.org, which is a private mailing list with the GNOME internship coordinators.

This is a repost from https://discourse.gnome.org/t/deadline-sept-20-2023-call-for-mentors-for-outreachy-december-23-march-24-cohort/16748

August 21, 2023

Visualizing Scheduler Details

One thing we’ve wanted for a while in Sysprof is the ability to look at what the process scheduler is doing. It can be handy to see what processes where switched and how they may be dependent on one-another. Previously, I’d fire up kernelshark for that as it’s a pretty invaluable tool. But having scheduler data inline with everything else you capture is too useful to pass up.

So here we have the sched:sched_switch tracepoint integrated into Sysprof marks so you can correlate that with the rest of your recording.

Scheduled processes displayed in a time series, segmented by CPU.

Fedora IPU6 black image issue

I have just become aware that Fedora users using the packaged IPU6 camera stack are having an issue where the output from the camera is black. We have been updating the stack and the new version of ipu6-camera-bins has hit the stable updates repo, while the matching new version of ipu6-camera-hal is currently in the updates-testing repo.

This causes the versions of ipu6-camera-bins vs ipu6-camera-hal to get out of sync (unless you have updates-testing enabled) which leads to the camera output being all black

You can fix this issue by running the following command:

sudo dnf update --enablerepo=rpmfusion-nonfree-updates-testing 'ipu6-camera-*'

Sorry about the inconvenience, we'll make sure to push both packages at the same time for the next set of updates.

I have tagged all the new ipu6-camera-hal builds to be moved to the stable update repositories, so on the next rpmfusion updates push this should be resolved.

August 19, 2023

GSoC 2023: Rust and GTK 4 Bustle Rewrite (Week 9 & 10)

Thumbnail

Progress Made

I am thrilled to announce that a lot of progress happened once again over the past few weeks! Starting with the most important thing, the diagram, a lot of bugs has been squashed and the rendering performance is now improved to a usable and merge-able state.

Diagram

Aside from that, it now has a header that doesn't overlay on the diagram, the drawings now properly adjust to text scale factor changes, the signal arrows are now drawn, and the rows are no longer clipped into the row titles.

My mentor, Bilal, also worked on adding tags, the colored circles in the diagram, so it is easier to determine the category of a message.

On the general UI side, there is now a proper loading page that is shown while waiting for the PCAP files to show.

Loading Page

However, that may not be as useful as we also landed a patch that speeds up loading by up to 32 times; that's from 16 seconds on average to 500 milliseconds, when loading a 2.2 MB file.

There is also now a waiting page that shows before the first message arrives while recording.

Waiting Page

We also landed statistics window which shows frequencies of messages, durations of method calls, and sizes information of each message.

Statistics Frequencies Page

Statistics Durations Page

Statistics Sizes Page

My mentor, Bilal, also worked on adding information on the details view including the body size and signature, and the response time from method return to its call.

Details View

There is also now a way to save the PCAP file as a DOT graph file, which can be used to generate a graph using Graphviz to visualize interactions between bus names like the following:

DOT Graph

On the plumbing side, saving to PCAP file is now asynchronous, avoiding blocking the UI. We also ported from using libpcap to a pure Rust library, removing unsafe code and simplifying the codebase. Furthermore, the timestamp computation is now more accurate.

As part of making debugging in Bustle easier, I have also submitted a patch to zbus to simplify the printing of bus names.

Finally, I started prototyping services filtering. It is still a proof of concept and does not look great, but it already functions and is a good starting point for next week.

Services Filtering

Plans for the Following Weeks

The following weeks will be the last days of GSoC and I will be focusing on finishing up the remaining tasks. With the most difficult part of this GSoC project done, most of the tasks left, aside from services filtering, are optimizing performance and cleaning up the codebase.

There are also a few more things to fix such as a proper name owner changed signal handling, drawing method call arc regardless if the row is not drawable, properly killing the dbus-monitor process, and propagating user-facing errors.

I would also like to add features such as adding a button that scroll to the method call message of a method return message or vice-versa, porting to use the new GskPath APIs, and adding a way to open multiple diagrams at once via tabs and multiple windows.


That's all for this week. Thanks for reading!

Motion Blur for Half-Life Video Recording with Vulkan

Video of a Half-Life movement map run, recorded with motion blur.

Half-Life is an award-winning first-person shooter from 1998. One of the many things setting it apart is a fluid player movement system. You can gradually build up speed in the air by turning left and right, and if you jump as soon as you hit the ground, you keep all of that speed.

These techniques, called strafing and bunny-hopping, unlocked a whole new dimension to the game, and spawned a big community of mappers and players. There are hundreds of custom maps with challenging obstacles designed to test one’s movement skills, and thousands of players competing to finish them as fast as possible.

The main hubs for these maps and records are Xtreme-Jumps1 and Cosy-Climbing. They are centered around Counter-Strike 1.6, which has similar movement mechanics to Half-Life, but paced slower with a jump-speed limit and some tweaks to the acceleration.

People generally record their runs to in-game demo files. They are very lightweight, making them easy to store and share. Players can then capture demos to videos. There are YouTube channels uploading videos of the best, or notable runs and edits, like the Xtreme-Jumps channel.

Since this gameplay style is focused on movement, it has been customary to use motion blur for video recordings. When done right, it can make the video look smoother and nicer to watch; here’s such an edit (volume warning) for example. Though, ultimately, motion blur is a subjective preference, and plenty of people will tell you how it is completely unwatchable. Part of this, I suspect, comes from frequent cases of motion blur done wrong. I shudder at the vision of someone dropping a 60 frames-per-second (FPS) video into a 30 FPS Vegas project, and leaving frame blending on its default enabled setting.

How Motion Blur Works #

The simplest way to get motion blur is to capture the video at a higher frame rate, then blend together multiple frames for every output video frame. And when dealing with a closed-source game engine, there actually isn’t much else you can do. Thankfully, Half-Life demos play back smoothly at any frame rate, regardless of the FPS the player had when running2, so this approach works perfectly.

Let’s explore how several frames combine into the final frame. The following interactive widget shows the final frame at the top, and a set of sixty consecutive sub-frames, or samples, below. Green outlines show which of the samples are blended together to form the final frame. Even though motion blur is best experienced in motion, this still-frame demo should make it clear how it works under the hood.

Please visit the blog post page to view this interactive element.

There are two sliders corresponding to two of the most important parameters for controlling the motion blur.

The top one controls the video capturing FPS, usually called “samples per second”, or “SPS” for short, in the context of frame blending. A low SPS value will produce clearly visible copies of the same objects, making it easy to tell apart the individual frames in the final composite. A high SPS value on the other hand will make the composite smooth and nice to look at in the final video.

The bottom slider controls the amount of blur. It is called “exposure” because it is analogous to exposure in photography and filming. Higher exposure means more sub-frames are blended together. Compare low exposure giving a mostly-clear still frame and high exposure resulting in a lot of blur as all samples are included.

Real-world exposure lets the camera sensor take in light continuously while the shutter is open, but game video capturing is discrete and can only use the samples that were recorded. This is why low SPS makes individual sub-frames visible—they are the only samples we have, with no in-between contents available.

So, high SPS is extremely important for good motion blur. When the player is moving or spinning fast, you need a lot of samples to blend together a smooth-looking frame without obvious edges. This means recording the video at a very high FPS. For Half-Life movement-oriented maps, you want to go up to at least around 3600 FPS to get a decent result regardless of what’s happening on the screen. If you can only capture the video at 60 real-time FPS, it will take you sixty times longer to capture the demo at 3600 FPS compared to its real-time duration. That is, an hour of recording per every minute of the demo. Basically, you really want your recording to go as fast as your computer allows, without being limited to real-time.

This problem was one of the main drivers for me to explore and make my own Half-Life video recording tools, first hl-capture, then bxt-rs. Frame blending is included in the widely-used HLAE tool, which has been around for many years. Unfortunately, HLAE had extremely slow recording speed back then (it is considerably better nowadays), making it unreasonable to use high FPS for motion blur. This is why so many of the older movement videos have clearly visible frame blending artifacts.

In the previous post about bxt-rs video recording, I wrote about how these kinds of tools can capture video much faster than real-time. Now let’s see how I added motion blur to the process.

Frame Blending in bxt-rs #

Frame blending is really just per-pixel averaging of multiple frames. If you know anything about how GPUs work, you can immediately see that this is the perfect task to do on a GPU: a ton of mutually-independent computations that can be done in parallel. Better still, the input frames are already on the GPU: the game has just rendered them. This means that we can avoid the costly data transfers between the GPU video memory and the main memory, making the whole process practically instantaneous. In fact, the interactive widget above is basically doing the same computation in real-time, as you’re dragging the sliders.

In bxt-rs I copy the frame rendered by the game into a texture shared between OpenGL and Vulkan. The Vulkan part then converts the pixel values from the RGB format to YUV 4:2:0 used in video files, downloads the result into the main memory, and hands it over to FFmpeg for video encoding.

The frame blending step will live in Vulkan, between receiving a new frame from OpenGL and pixel format conversion. We only need to output and encode the final blended frame, so intermediate sub-frames can stay in RGB.

Color Accumulation #

We don’t actually need all sixty or so sub-frames at once in video memory. We can allocate one buffer for the output frame (the sampling buffer), then accumulate the intermediate frames into it one by one as they come in. Averaging pixel values means summing them up and dividing by the total count, which is equivalent to dividing every value by the total count first, and summing them together afterwards. Since we know the total count in advance (it’s SPS multiplied by exposure), we can do this division and addition as the frames come in, and get the correct averaged result in the end.

I chose the 16-bit RGB format for the sampling buffer. This allows up to 257 sub-frames with no precision loss due to quantization3, which is more than enough. By default, bxt-rs records at 7200 FPS, which means up to 120 sub-frames with an output FPS of 60, or up to 240 sub-frames with an output FPS of 30—both below 257.

let create_info = vk::ImageCreateInfo {
    // Use the 16-bit RGB format in UNORM mode.
    format: vk::Format::R16G16B16A16_UNORM,
    usage:
        // For updating during the sampling stage.
        | vk::ImageUsageFlags::STORAGE
        // For clearing.
        | vk::ImageUsageFlags::TRANSFER_DST
        // For reading during YUV conversion.
        | vk::ImageUsageFlags::SAMPLED,
    // ...
};
let image_sample = device.create_image(&create_info, None)?;

UNORM lets me treat pixel values as floating point numbers from 0 to 1 in the shader, without having to round them manually.

The shader itself is pretty simple. Its inputs are the new sub-frame from the game, the sampling buffer and the weight factor. The code loads a pixel from the new sub-frame, multiplies it by the weight, and adds it into the sampling buffer.

#version 450

// Copied from the color conversion shader where it gave the best performance.
#define WORKGROUP_SIZE 4
layout (local_size_x = WORKGROUP_SIZE, local_size_y = WORKGROUP_SIZE, local_size_z = 1) in;

// Frames coming from the game are rgba8.
layout (binding = 0, rgba8) uniform readonly image2D image_frame;
// Our intermediate buffer is rgba16.
layout (binding = 1, rgba16) uniform image2D image_sample;

// Weight is set for every frame at runtime.
layout (push_constant) uniform push_constants {
    float weight;
};

void main() {
    ivec2 size = imageSize(image_frame);
    uint width = uint(size.x), height = uint(size.y);

    uint x = gl_GlobalInvocationID.x, y = gl_GlobalInvocationID.y;
    if (x >= width || y >= height)
        return;

    ivec2 coords = ivec2(x, y);

    vec4 frameColor = imageLoad(image_frame, coords);
    vec4 sampleColor = imageLoad(image_sample, coords);

    // Accumulate the frame into the intermediate buffer.
    vec4 newColor = sampleColor + frameColor * weight;
    imageStore(image_sample, coords, newColor);
}

Using multiplication instead of division is just a common thing to do. A usual justification is that it’s faster this way. I’m not sure how much this is still true, especially for shader code4, but anyhow.

I can use image2D rather than sampler2D for the input image, since in this shader I read every pixel independently of the rest. This lets me avoid making the whole Vulkan combined image sampler required for a sampler2D.

The weight can vary between frames, so I pass it as a Vulkan push constant. This is because exposure can cut a sub-frame in half, causing its weight to be reduced. That might seem like an edge case which can be solved by rounding the exposure to always cover full sub-frames. You can indeed do that for capturing demos. However, bxt-rs also supports capturing TAS playback where the game’s FPS cannot be changed, so the frame blending code must work with any timing thrown at it.

Recording Loop #

As you may find in the previous post, the video recording loop without frame blending looks somewhat like this:

Run game tick
Run game tick
Draw frame
Draw frame
Capture frame
Capture frame
Swap buffers
Swap buffers
Copy to
intermediate
Copy to...
Convert colors
Convert colors
Transfer to CPU
Transfer to CPU
Send to FFmpeg
Send to FFmpeg
Second Thread
Second Thread
Text is not SVG - cannot display

Every frame is copied to an intermediate buffer, because we don’t know its duration until the next frame. Then, bxt-rs converts its colors from RGB to YUV 4:2:0 on the GPU, transfers the result to the main memory, and sends it to FFmpeg for encoding.

With frame blending, the steps are a bit different:

Run game tick
Run game tick
Draw frame
Draw frame
Capture sub-frame
Capture sub-frame
Swap buffers
Swap buffers
Copy to intermediate
Copy to intermediate
Convert colors
Convert colors
Transfer to CPU
Transfer to CPU
Send to FFmpeg
Send to FFmpeg
Second Thread
Second Thread
Yes
Yes
Is frame complete?
Is frame complete?
Accumulate
Accumulate
Text is not SVG - cannot display

After obtaining the frame duration, bxt-rs computes the weight and accumulates the sub-frame into the sampling buffer. For most frames this is where the process ends. The recorder keeps track of accumulated frame time. As soon as we’ve accumulated a full output frame worth of sub-frames, it’s sent through the usual steps of color conversion and FFmpeg encoding. The sampling buffer is then cleared to zeroes, ready to accumulate for the next video frame.

There are a few edge cases to take care of. First, as I mentioned before, the sub-frame can be covered by the exposure time only partially. This is solved simply by reducing its accumulation weight: instead of full sub-frame durationexposure, the weight becomes exposed sub-frame durationexposure.

Then, a sub-frame may actually be split in half by an output frame boundary, and even span several output frames all by itself. For example, this can happen when a TAS uses a long 4 FPS (0.25 ms) frame for a trick, which needs to be recorded as several output frames in a 60 FPS video. Turns out this is also easy to handle: since we store the frame in an intermediate buffer, we can just tell the sampling code to accumulate it multiple times with different weights, while writing output frames in between.

output frames
output frames
one long sub-frame
one long sub-frame
acc(0.5)
encode()
acc(0.5)...
acc(1.0)
encode()
acc(1.0)...
acc(0.5)
acc(0.5)
time
time
Text is not SVG - cannot display

Synchronization #

Since we store the sub-frame in an intermediate buffer, the accumulation step can run in parallel to the rendering and capturing of the next game frame. Here, “in parallel” means both on CPU (with a second thread) and on GPU. We therefore need to synchronize the GPU access to the intermediate buffer, so that the accumulation step won’t try to read from it at the same time as the capturing step is writing to it.

For that we use the Vulkan memory barriers5. The intermediate frame processing in the accumulation step starts with a transfer → shader barrier and ends with a shader → transfer barrier.

let intermediate_frame_barrier = vk::ImageMemoryBarrier::builder()
    // Make the memory write performed by a transfer available.
    .src_access_mask(vk::AccessFlags::TRANSFER_WRITE)
    // Make the memory visible for a shader read.
    .dst_access_mask(vk::AccessFlags::SHADER_READ)
    .old_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
    .new_layout(vk::ImageLayout::GENERAL)
    .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
    .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
    .image(self.image_acquired)
    .subresource_range(vk::ImageSubresourceRange {
        aspect_mask: vk::ImageAspectFlags::COLOR,
        base_mip_level: 0,
        level_count: 1,
        base_array_layer: 0,
        layer_count: 1,
    });
self.device.cmd_pipeline_barrier(
    self.command_buffer_accumulate,
    vk::PipelineStageFlags::TRANSFER,
    vk::PipelineStageFlags::COMPUTE_SHADER,
    vk::DependencyFlags::empty(),
    &[],
    &[],
    &[*intermediate_frame_barrier],
);

// Do the processing...

let intermediate_frame_barrier = vk::ImageMemoryBarrier::builder()
    // We were not writing anything, no memory write to make available.
    .src_access_mask(vk::AccessFlags::empty())
    // Make the memory visible to a transfer write.
    .dst_access_mask(vk::AccessFlags::TRANSFER_WRITE)
    .old_layout(vk::ImageLayout::GENERAL)
    .new_layout(vk::ImageLayout::TRANSFER_DST_OPTIMAL)
    .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
    .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
    .image(self.image_acquired)
    .subresource_range(vk::ImageSubresourceRange {
        aspect_mask: vk::ImageAspectFlags::COLOR,
        base_mip_level: 0,
        level_count: 1,
        base_array_layer: 0,
        layer_count: 1,
    });
self.device.cmd_pipeline_barrier(
    self.command_buffer_accumulate,
    vk::PipelineStageFlags::COMPUTE_SHADER,
    vk::PipelineStageFlags::TRANSFER,
    vk::DependencyFlags::empty(),
    &[],
    &[],
    &[*intermediate_frame_barrier],
);

Note that this command buffer effectively returns the intermediate frame back to the same state it was in. This trick lets me accumulate either once or multiple times in a row without dealing with any sort of synchronization edge cases.

But wait, the intermediate frame is not the only resource used for accumulation. What about the sampling buffer? It has two possible paths: either it can go for color conversion, where it will be read from, or we might accumulate into it again next sub-frame. Turns out this is not a problem: we can set the destination access mask on the closing barrier to both shader reads (if we go for color conversion) and shader writes (if we accumulate more).

// There's no opening barrier, actually,
// as the sampling buffer is already synchronized.

// Do the processing...

let sampling_buffer_barrier = vk::ImageMemoryBarrier::builder()
    // Make the accumulation shader writes available.
    .src_access_mask(vk::AccessFlags::SHADER_WRITE)
    // Make the memory visible to both shader reads and writes.
    .dst_access_mask(vk::AccessFlags::SHADER_READ | vk::AccessFlags::SHADER_WRITE)
    .old_layout(vk::ImageLayout::GENERAL)
    .new_layout(vk::ImageLayout::GENERAL)
    .src_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
    .dst_queue_family_index(vk::QUEUE_FAMILY_IGNORED)
    .image(self.image_sample)
    .subresource_range(vk::ImageSubresourceRange {
        aspect_mask: vk::ImageAspectFlags::COLOR,
        base_mip_level: 0,
        level_count: 1,
        base_array_layer: 0,
        layer_count: 1,
    });
self.device.cmd_pipeline_barrier(
    self.command_buffer_accumulate,
    // This barrier will wait for the compute shader to finish.
    vk::PipelineStageFlags::COMPUTE_SHADER,
    // The next compute shader should wait for this barrier.
    vk::PipelineStageFlags::COMPUTE_SHADER,
    vk::DependencyFlags::empty(),
    &[],
    &[],
    &[*sampling_buffer_barrier],
);

We use two buffer, and we synchronized both of them. We’re done, right?

That’s what I thought when I wrote this code, and the validation layers seemed to agree. However, recording a video crashed my AMD driver a few seconds into the process.

After trying several things, re-reading the Vulkan synchronization blog-post and quadruple-checking my barriers, I was out of ideas. I was too lazy to make a C reproducer for a Mesa bug report, so I added a fence wait, blocking the CPU until the GPU finishes the accumulation step. This had a performance cost, but seemed to fix the issue.

// XXX: As far as I can tell, waiting for a fence here
// should not be required (and it makes the process quite
// slower). Unfortunately, I'm getting GPU fence timeouts if
// I don't do it. Maybe there's a synchronization bug in the
// code, but I don't see it, and the validation layers are
// silent too.
let create_info = vk::FenceCreateInfo::default();
let fence = self.device.create_fence(&create_info, None)?;

let command_buffers = [self.command_buffer_accumulate];
let submit_info = vk::SubmitInfo::builder().command_buffers(&command_buffers);
self.device
    .queue_submit(self.queue, &[*submit_info], fence)?;

{
    // I use tracing spans with tracing-chrome for profiling.
    let _span = info_span!("wait for fence").entered();

    // Block everything and wait until the GPU finishes the work.
    self.device
        .wait_for_fences(&[fence], true, u64::max_value())?;
}

self.device.destroy_fence(fence, None);

Several months passed, players were recording videos with motion blur just fine, but The Fence kept sitting in my head. This recording code is supposed to be really fast, and I have this useless idle time right in the middle of the hottest loop.

Just look at the performance profile6 with no Fence:

The X axis on these graphs represents time, and colored rectangles correspond to particular ranges of code that I’ve annotated, usually functions. So if the rectangle is short, that means the function took little time, and the longer a rectangle is, the longer that function took. There are two distinct rows on the graph separated with a gray line—these are the two threads: the recording thread at the top and the main game thread at the bottom.

This graph above shows four sub-frames during one of the recording sessions. All empty space between the occasional colored area is the game’s own processing. That is, my video recording logic takes almost no time at all—less than 10% of overhead.

Now compare that to The Fence:

Ouch! Almost all bxt-rs time is the wait, and sometimes it even eats into (and delays!) the next frame’s processing. It’s helped only by the fact it happens on another thread. Certainly in other recording scenarios this overhead will be much worse. Hopefully you can see why this Fence had firmly leased a plot in the back of my mind.

Around that time I was testing my relatively slow ThinkPad T495s with a 4K screen. It’s got no discrete GPU and its AMD Ryzen 5 3500U CPU is an ultra-low-power model, which means lower performance. I was occasionally getting random GPU crashes while using the desktop. I found an AMDGPU bug report about it and jumped in to help diagnose the problem.

There seemed to be several underlying issues at play, but for my laptop I found a consistent reproducer with a heavy Blender file. The developers pinned one of the issues down to a bug in the threaded graphics driver implementation. The workaround was to disable the threads with GALLIUM_THREAD=0, which for me fully fixed the Blender reproducer.

This Blender crash visually looked exactly the same as my sampling video recording crash, so naturally I decided to give it a go. I removed The Fence, set GALLIUM_THREAD=0, started recording my test demo with frame blending. It recorded fine. Hooray, mystery solved! It was a Mesa threading bug. I committed the fence removal and documented the workaround in the README.

A week later an NVIDIA user reports corrupted video frames during recording.

Well.

Let’s try with The Fence back, I guess.

No corruption.

At this point I was rather lost. For me, the validation layers were completely silent no matter what I tried, both when my GPU worked fine and when it crashed. I decided to give it one last chance, and asked my NVIDIA user friend to record without the fence, and with the validation layers.

2023-07-11T15:31:23.886949Z  INFO acquire:acquire_image: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkBeginCommandBuffer-commandBuffer-00049 ] Object 0: handle = 0xaded368, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x84029a9f | vkBeginCommandBuffer() on active VkCommandBuffer 0xaded368[] before it has completed. You must check command buffer fence before this call. The Vulkan spec states: commandBuffer must not be in the recording or pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkBeginCommandBuffer-commandBuffer-00049)
2023-07-11T15:31:23.887111Z  INFO acquire:acquire_image: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkQueueSubmit-pCommandBuffers-00071 ] Object 0: handle = 0xaed7188, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x2e2f4d65 | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0xaded368[] is already in use and is not marked for simultaneous use. The Vulkan spec states: If any element of the pCommandBuffers member of any element of pSubmits was not recorded with the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, it must not be in the pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkQueueSubmit-pCommandBuffers-00071)
2023-07-11T15:31:23.888095Z  INFO accumulate:accumulate{weight=0.008333333767950535}: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkBeginCommandBuffer-commandBuffer-00049 ] Object 0: handle = 0xadf1188, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x84029a9f | vkBeginCommandBuffer() on active VkCommandBuffer 0xadf1188[] before it has completed. You must check command buffer fence before this call. The Vulkan spec states: commandBuffer must not be in the recording or pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkBeginCommandBuffer-commandBuffer-00049)
2023-07-11T15:31:23.888272Z  INFO accumulate:accumulate{weight=0.008333333767950535}: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkQueueSubmit-pCommandBuffers-00071 ] Object 0: handle = 0xaed7188, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x2e2f4d65 | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0xadf1188[] is already in use and is not marked for simultaneous use. The Vulkan spec states: If any element of the pCommandBuffers member of any element of pSubmits was not recorded with the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, it must not be in the pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkQueueSubmit-pCommandBuffers-00071)
2023-07-11T15:31:23.912888Z  INFO acquire:acquire_image: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkBeginCommandBuffer-commandBuffer-00049 ] Object 0: handle = 0xaded368, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x84029a9f | vkBeginCommandBuffer() on active VkCommandBuffer 0xaded368[] before it has completed. You must check command buffer fence before this call. The Vulkan spec states: commandBuffer must not be in the recording or pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkBeginCommandBuffer-commandBuffer-00049)
2023-07-11T15:31:23.913049Z  INFO acquire:acquire_image: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkQueueSubmit-pCommandBuffers-00071 ] Object 0: handle = 0xaed7188, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x2e2f4d65 | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0xaded368[] is already in use and is not marked for simultaneous use. The Vulkan spec states: If any element of the pCommandBuffers member of any element of pSubmits was not recorded with the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, it must not be in the pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkQueueSubmit-pCommandBuffers-00071)
2023-07-11T15:31:23.914085Z  INFO accumulate:accumulate{weight=0.008333333767950535}: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkBeginCommandBuffer-commandBuffer-00049 ] Object 0: handle = 0xadf1188, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x84029a9f | vkBeginCommandBuffer() on active VkCommandBuffer 0xadf1188[] before it has completed. You must check command buffer fence before this call. The Vulkan spec states: commandBuffer must not be in the recording or pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkBeginCommandBuffer-commandBuffer-00049)
2023-07-11T15:31:23.914266Z  INFO accumulate:accumulate{weight=0.008333333767950535}: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkQueueSubmit-pCommandBuffers-00071 ] Object 0: handle = 0xaed7188, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x2e2f4d65 | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0xadf1188[] is already in use and is not marked for simultaneous use. The Vulkan spec states: If any element of the pCommandBuffers member of any element of pSubmits was not recorded with the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, it must not be in the pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkQueueSubmit-pCommandBuffers-00071)
2023-07-11T15:31:23.920305Z  INFO acquire:acquire_image: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkBeginCommandBuffer-commandBuffer-00049 ] Object 0: handle = 0xaded368, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x84029a9f | vkBeginCommandBuffer() on active VkCommandBuffer 0xaded368[] before it has completed. You must check command buffer fence before this call. The Vulkan spec states: commandBuffer must not be in the recording or pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkBeginCommandBuffer-commandBuffer-00049)
2023-07-11T15:31:23.920462Z  INFO acquire:acquire_image: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkQueueSubmit-pCommandBuffers-00071 ] Object 0: handle = 0xaed7188, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x2e2f4d65 | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0xaded368[] is already in use and is not marked for simultaneous use. The Vulkan spec states: If any element of the pCommandBuffers member of any element of pSubmits was not recorded with the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, it must not be in the pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkQueueSubmit-pCommandBuffers-00071)
2023-07-11T15:31:23.920610Z  INFO accumulate:accumulate{weight=0.008333333767950535}: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkBeginCommandBuffer-commandBuffer-00049 ] Object 0: handle = 0xadf1188, type = VK_OBJECT_TYPE_COMMAND_BUFFER; | MessageID = 0x84029a9f | vkBeginCommandBuffer() on active VkCommandBuffer 0xadf1188[] before it has completed. You must check command buffer fence before this call. The Vulkan spec states: commandBuffer must not be in the recording or pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkBeginCommandBuffer-commandBuffer-00049)
2023-07-11T15:31:23.920780Z  INFO accumulate:accumulate{weight=0.008333333767950535}: bxt_rs::vulkan: ERROR VALIDATION Validation Error: [ VUID-vkQueueSubmit-pCommandBuffers-00071 ] Object 0: handle = 0xaed7188, type = VK_OBJECT_TYPE_DEVICE; | MessageID = 0x2e2f4d65 | vkQueueSubmit(): pSubmits[0].pCommandBuffers[0] VkCommandBuffer 0xadf1188[] is already in use and is not marked for simultaneous use. The Vulkan spec states: If any element of the pCommandBuffers member of any element of pSubmits was not recorded with the VK_COMMAND_BUFFER_USAGE_SIMULTANEOUS_USE_BIT, it must not be in the pending state (https://www.khronos.org/registry/vulkan/specs/1.3-extensions/html/vkspec.html#VUID-vkQueueSubmit-pCommandBuffers-00071)

…oh. The important part here is: vkBeginCommandBuffer() on active VkCommandBuffer 0xaded368[] before it has completed. Without The Fence, my recording is calling accumulate so fast that the GPU shader from the last call is still running. 🤦 To make matters worse, the same apparently happens with copying the game’s frame to the intermediate buffer. The Fence avoided the issue by waiting for the shader completion right after starting it.

Well! The proper fix was rather straightforward, and in fact pretty similar to The Fence. We still have to wait for the GPU to finish, but we don’t have to wait right after starting the work. Instead, we can get a bit of parallelization back by waiting right before starting the next batch of work. This gets most of the fence-less performance back while working perfectly fine on both AMD and NVIDIA.

Here’s the performance graph to confirm it:

Looks almost the same as the fence-less graph above, save for the tiny pink areas where the GPU is waiting for the previous frame’s command buffer.

Actually, further into the recording my GPU starts exhibiting some funny behavior where the waits gradually increase, and then reset back to nothing at an output frame boundary:

My guess here is that the GPU heats up and starts downclocking, so the shaders start taking longer to run. And at an output frame boundary the long main memory transfer gives the GPU some breathing room to cool back down.

And on my NVIDIA user friend’s setup the wait times seem straight up random:

Well, either way, the issue is fixed, and I’m parallelizing as much as possible, as confirmed by the performance measurements.

I’m still not sure why the validation layers didn’t catch the bug on my system. Maybe they weren’t recent enough, so this check hadn’t been added yet? Regardless, it seems a good idea to run the validation layers on different drivers and GPU vendors before declaring the code working fine.

Overhead #

Measuring overall video recording performance in Half-Life is difficult because, when the recording overhead is sufficiently low, the speed is fully dominated by how fast your computer can actually play a particular demo file. It’s especially challenging on AMD GPUs which are known to have worse performance for legacy OpenGL games such as Half-Life, compared to NVIDIA. Like, here on my system I can look into one corner of the map and get 2000 FPS, then look into a different corner and get 200 FPS.

To get a somewhat reasonable measure of performance, I picked a specific demo (my kz_ytt_ancient TAS, same as the video at the very top of this post) and played it with and without capturing on two of my laptops. I used a resolution of 2560×1440 with 60 output FPS, which is a fairly normal recording target for today.

For these measurements I used the Tracy profiler, which is a low-overhead game-oriented profiler. It automatically shows histograms and statistics for frame times and regions, making it very convenient for performance analysis.

First, the results on my fast laptop: Lenovo Legion 7 Gen 7 AMD with a Ryzen 7 6800H CPU with an RX 6700M discrete GPU. Here I used 7200 SPS, the bxt-rs default. During recording, the demo is played at a fixed timestep of 7200 FPS, so to measure the recording overhead, for the non-recording run I also play the demo at 7200 FPS.

Let’s start with normal demo playback with no recording. Please note that all histograms below use logarithmic scale for the X axis.

These are frame time statistics: they show a histogram of how long each individual frame took to process, as well as the mean and the median, and the equivalent FPS values.

As you can see there’s quite a bit of variation in frame times (note the logarithmic scale!), but the median frame time is 1.21 ms, or 828 FPS. This is measured across 211,876 total frames.

Next, let’s run the same demo while recording a video with motion blur.

The distribution looks similar, but there’s a tail on the right. This tail contains the one-in-120 frames which complete the sampling buffer and cause an output frame to be transferred to the main memory and sent to FFmpeg for encoding. However, this happens infrequently enough, so the median frame time increased by just 0.16 ms, or 160 µs, compared to no recording.

Let’s look at the overhead specifically.

These are statistics for just the bxt-rs recording code. They correspond to extra work done on the main thread during recording on top of the normal game rendering.

As you can see, this overhead is mostly sitting at around 113 µs, just like the frame time difference told us. And once again there’s a small tail on the right, corresponding to the output frame main memory transfer and encoding.

Overall this overhead accounts for 27.89 s out of 4:12.9 of recording time, or about 11% for this demo and setup.

Now let’s look at the results from my ThinkPad T495s with its Ryzen 5 3500U CPU and no discrete GPU. I reduced the SPS from 7200 to 1800 here because the rendering was just too slow unfortunately, and I didn’t feel like waiting that long.

First, the statistics without video recording.

You can immediately see the FPS taking a 4× drop compared to the beefier laptop. There’s also a tail on the right: I found that the map contains one specific spot that the integrated GPU really struggles with. In fact, it’s clearly visible as red on the frame times chart that Tracy provides:

Now let’s look at the graphs while recording with motion blur.

Here you can see the median frame time increased from 4.92 ms all the way up to 15.32 ms. Looks like bxt-rs recording has a sizeable overhead on this setup.

Hm, doesn’t look too bad, just 1.36 ms in the median. Now, one thing to remember is that since we’re recording at four times fewer SPS, we’re having a long output frame once every 30 frames rather than once every 120 frames, so they have a larger compounding impact.

But it seems that there’s a more important issue at play. My guess is that the GPU work for accumulating the sampling buffer every frame overlaps with and slows down the GPU work that goes into rendering the game—the integrated GPU isn’t powerful enough to do both at the same time without a slowdown.

Looking at the fence wait statistics, which show how long the second thread is waiting for the last frame’s GPU work to finish, seems to confirm this:

While plenty of waits are very short (the previous frame’s GPU work had already completed by the time the next frame has started, so there’s no wait), there’s also a big chunk in the higher ranges, up to 10 ms. Compare this to the statistics on the fast laptop:

Yeah, the discrete GPU has got no problem at all doing sampling accumulation and game rendering at the same time.

To sum up, for this particular testing scenario I’m seeing about 15% recording overhead for a laptop with a discrete GPU, and about 200% recording overhead for a laptop with a weak integrated GPU. Since with this setup the fast laptop manages around 730 real-time FPS during recording, and the demo plays back at 7200 FPS, the whole process goes about ten times slower than real-time.

Sidenote: I love how snappy everything in Tracy is. All UI zooming and panning and resizing works in real-time with little dropped frames on my 165 Hz screen, despite the seemingly huge amounts of data involved. I guess Tracy developers put their own work to excellent use on Tracy itself.

Conclusion #

I released video recording with motion blur as part of bxt-rs 4.0. It ended up a bit overshadowed by the big new piece of functionality that is the TAS Editor 2, but most of the people who needed video recording have already been using the development snapshots anyway.

There have been a bunch of videos recorded with this motion blur implementation already, and I’m glad to see that it had caught on, with some channels using it even for the most recent uploads. This is mainly thanks to my aforementioned NVIDIA user friend recommending the tool to people in the Counter-Strike circles, while also implementing several features they’ve been asking for.

I personally used the motion blur recording for my most recent TASes at 8K60 resolution just fine with no issues. This is roughly how fast that goes:

Remember that this is recording at 8K resolution, that is 7680×4320 pixels, at 7200 FPS. The short pauses you might notice are the moments when an output frame is completed and sent over to the main memory and to FFmpeg. Furthermore, this is on an AMD GPU which is known to have poorer performance with the legacy OpenGL used by Half-Life. Pretty fast all things considered.

The final output video for that is https://youtu.be/GnUh6b5NxNo.

Of course, there’s still potential for improvement.

For one thing, as I noted in the previous post, it’s possible to do encoding on the GPU using a hardware encoder. This will eliminate the CPU transfer of the full unencoded frame, which is very likely the main contributing factor to those short pauses you see on the video above. For very high resolutions it would also be potentially faster than libx264 (although I guess it would run into the hardware encoder size limits), while allowing to use newer compression standards, like AV1, which are much more efficient at high resolution.

Then there’s the question of SPS. My chosen default of 7200 SPS is probably way too high for most demos, and you can get the same quality video with lower values. This would mean the game has fewer frames to render, which is the biggest bottleneck in the recording process.

However, it’s quite difficult to estimate a good SPS value ahead of time as it would require something like computing the optical flow (pixel movement vectors) for every frame of the demo to find the largest jump. You have to do it ahead of time because Half-Life’s demo interpolation for some reason really dislikes it when you change the FPS mid-playback, and produces very uneven videos. I went with a default SPS this high as a safe option, because the relatively small recording overhead allows me.

One very interesting idea is exposure greater than a single frame. It might not make much sense with the real world film, but in-game you just need to take some samples from the previous frames and accumulate them into the next frame. This would allow, for example, having the same amount of blur on a 120 FPS video as on a 60 FPS video with a full-frame exposure. A full-frame exposure on a 120 FPS video is equivalent to a half-frame exposure on a 60 FPS video, so right now the best you can get is half the blur.

Unfortunately, as far as I can tell, the general multi-frame exposure case requires keeping around all individual sub-frames to be able to pick and accumulate them for the next frame. If exposure is limited to whole frames, then it should be possible to only keep around one partially-accumulated frame for every whole frame of exposure, which is much more reasonable, but still complicated to implement.

Either way, I’m glad to have this code released and this blog post out. It’s been a good Vulkan diving practice and an interesting parallelism and correctness exercise. I hope you enjoyed the read and learned something useful!

Appendix: Interactive Widget #

The interactive widget above uses WebGL and a frame averaging shader to simulate the sampling process in real-time. You can take a look at the page source code, it shouldn’t be too complicated.

All 60 input frames are packed into one big 3840×3600 atlas which the shader reads from. Apparently, Chrome on Android (but not Firefox on Android) limits texture sizes to 4096×4096 for all devices due to an issue on some specific GPUs, so the atlas had to fit into that limit.

The whole interactive widget thing was inspired by the fantastic, spectacular posts by Bartosz Ciechanowski. Make sure to check them out at https://ciechanow.ski/ if you somehow haven’t stumbled upon them yet.


  1. Who unfortunately seem to have just recently lost access to their domain, https://xtreme-jumps.eu. They’re in process of rebuilding the site from scratch. ↩︎

  2. This is possible thanks to interpolation done by the Half-Life engine during demo playback. When watching a demo, the engine looks at recorded demo frames around the current playback timestamp, and interpolates the player’s position and angles, as well as other entities’ positions and angles. ↩︎

  3. Frames coming from the game are in 8-bit RGB format, which means the lowest color component value is 1255. The 16-bit RGB format expands this to represent values as low as 165535. When averaging together 257 color component values through accumulation, we divide every incoming value by 257. In the worst case we end up with the resulting value of 1255 × 1257 = 165535, which is still representable in our intermediate RGB-16 format. Many frames will have pixel values bigger than the one-above-black 1255, which can be divided by even higher numbers while remaining representable. ↩︎

  4. Even if it matters only for CPU code, it is still useful, as bxt-rs implements the same operations with the same multiplication on the CPU for the fallback path. ↩︎

  5. I heavily recommend this blog post explaining how to use Vulkan synchronization and memory barriers. ↩︎

  6. I use tracing and tracing-chrome to generate Chrome-tracing-compatible performance traces, then view them in Perfetto↩︎

August 18, 2023

Status update, 17/08/2023

Hello!
As often, these are some thoughts without any grand announcements to accompany them.

It’s just passed 5 years since I arrived in Santiago de Compostela, without much of a plan, and here I am. Summer a bit milder than last year which is great as I have got really back into cycling again. Compostela apparently has the highest ratio of tourists to locals at the moment in all Spain – 1.3 visitors for every resident, its very noticeable if you go to the old town. Meanwhile in the UK even Conservative news outlets now recommend leaving. I personally miss UK life but what I really miss is the UK of 2013, before the last decade of Conservative-led self destruction.

I already posted about GUADEC and not much has happened since. I have done some development on a Raspberry Pi-based home media server that, until recently, ran Raspbian and now runs Fedora IoT. I wanted to test an OSTree-based OS for a while and this seemed like a good opportunity as Fedora recently gained support for the Pi’s GPU.

The atomic updates with rpm-ostree work really nicely, it can be slow on ARM + SDcard, but that’s a good incentive to keep the base OS small and use containers for hosted services. This change in approach from “distro packages are the best option” to “containers are the best option” is what makes this migration non trivial.

Raspbian and Debian very much encompass the “traditional distro” model where all usecases should be satisfied with packages. Some folk still claim this to be the One True Way. In an ideal world I would agree. When it works well the results are great, the Kodi package on Raspbian is a great example which *just works*, thanks to hard work by many people. That said, Debian isn’t the newest collection of packages and Raspbian is not even based on the newest Debian release. It’s not even 64-bit by default. If I did have time to try and push these efforts forwards, where would I start?

Containers are not a panacea. Configuring services by adding snippets of config file into different environment variables can be maddening. But I am enjoying the “small distro” concept overall and finding suitable containers for different services hasn’t been difficult. So what about Kodi?

There is a Flatpak for Kodi and installing Flatpak on the Pi was not difficult. The Kodi Flatpak is only available for x86_64, though. How hard can it be to build for arm64?

Well, as always Hubert Figuiere already made a start. I updated his PR here. Building it locally on my laptop using flatpak-builder and qemu-system-arm took most of this week, and the resulting binary manages to load and display its UI, which is surprisingly good progress. The process doesn’t respond to keyboard input, so when I have a free minute I get to do some more debugging. Let’s see how it goes before I get bored.

#109 Managing Repositories

Update on what happened across the GNOME project in the week from August 11 to August 18.

GNOME Core Apps and Libraries

GLib

The low-level core library that forms the basis for projects such as GTK and GNOME.

Philip Withnall announces

macOS CI has been re-enabled for GLib, thanks to some welcome help and support from René de Hesselle

GNOME Circle Apps and Libraries

Alan reports

File Shredder v2.0.0 was finally released, the last version being released in October of last year.

  • The preferences dialog has been removed.
  • File Shredder now uses custom code that is included directly in the source.

Cartridges

Launch all your games

kramo reports

This week, I released Cartridges 2.2!

  • RetroArch support has been added, huge thanks to Rilic
  • Added the option to automatically clean up uninstalled games on import
  • Added the ability to undo an import, just to be safe :)

Check it out on Flathub!

Third Party Projects

Turtle

Manage git repositories in Nautilus.

Philipp says

Turtle 0.4 released!

Turtle is a tool to manage git repositories within Nautilus by providing emblems and context menus. For more complex operations it provides specific dialogs, i.e. commit, sync, log, settings, etc.

Version 0.4 marks a huge leap forward in its development: Almost all git operations can now be performed by Turtle in a convenient and graphical way.

Gradience

Change the look of Adwaita, with ease.

Daudix UFO says

Gradience 0.8.0-beta1 is finally available for download from Flathub Beta! it took a bit longer than we expected, but everything should be good now.

To install it, add flathub-beta remote and install Gradience from it:

flatpak remote-add --if-not-exists flathub-beta https://flathub.org/beta-repo/flathub-beta.flatpakrepo
flatpak install flathub-beta com.github.GradienceTeam.Gradience

To run the beta you might need to use this command:

flatpak run --branch=beta com.github.GradienceTeam.Gradience

See previous TWIG update for more info about 0.8.0-beta1

Login Manager Settings

Customize your login screen.

Mazhar Hussain announces

GDM Settings v3.3 was released with fixes for the following two bugs.

  • Changing background color didn’t work on GNOME 44
  • Background image was broken on widescreen/dual monitor setups with GNOME 44

Fractal

Matrix messaging app for GNOME written in Rust.

Kévin Commaille announces

Fractal 5.beta2 is out!

Fractal 5.beta2 is the second beta release 🎉 since the rewrite of Fractal to take advantage of GTK 4 and the Matrix Rust SDK, an effort that started in March 2021.

The most visible changes since Fractal 5.beta1 are:

  • Editing text messages ✏️
  • Logging in with homeservers that don’t support auto-discovery 🧐
  • A refactor of the login flow should avoid crashes when going back at any step 🔙
  • Sometimes two day dividers 📅 would appear next to each other without messages between them, this is now fixed

Of course, there are a also a lot of less visible changes, notably a lot of refactoring, 🐛 fixes and translations thanks to all our contributors, and our upstream projects.

As the version implies, this is still considered beta stage and might trigger crashes or other bugs 😔 but overall should be pretty stable 👍. It is available to install via Flathub Beta 📦, see the instructions in our README.

A list of blocking issues for the release of version 5 can be found in the Fractal 5 milestone on GitLab. All contributions are welcome!

Denaro

Manage your personal finances.

Nick says

Denaro V2023.8.0 is here! This release comes after months of hard work and includes many new features, including graphs!, and many fixes that make Denaro an even better money manager! Read about all the changes below :)

Here’s the full changelog:

  • Added graph visuals to the Account view as well as in exported PDFs
  • Added tags that can be used with transactions for finer management and filtering
    • Thanks @fsobolev
  • Added the option to select the entire current month as the date range filter
    • Thanks @CoffeeIsLife87 !
  • Added reminders for upcoming transactions
  • Improved the transaction description suggestion algorithm with fuzzy search
  • Fixed an issue where the help button in the import toast was not working
  • Fixed an issue where Denaro would crash if an account had incorrect formatted metadata
  • Fixed an issue where docs were not available when running Denaro via snap
  • Updated translations (Thanks to everyone on Weblate)!

Daikhan

Play Videos/Music with style.

Mazhar Hussain announces

TWIG-Bot

  • Daikhan received translation support and has already been translated into Turkish thanks to Sabri Ünal. Translations are done through Hosted Weblate.
  • Dropping multiple files into the player window works as expected now. Previously, only the last file would play.
  • The app shows a dialog explaining the situation if the user tries to open an unsupported file type e.g. a document or a supported file type (video or audio) with unsupported codec.

The flatpak received support for more codecs but it won’t/can’t be released until the Flathub build bot is fixed.

GNOME Foundation

Sonny reports

The following GNOME Circle requirement has been removed “No existing non-profit fiscal sponsor”. It came from an ambiguity in our software policy that is now solved. GNOME Circle developers are welcome to do fundraising how they see fit. Thanks to Sophie (she/her) for raising this issue with the board.

That’s all for this week!

See you next week, and be sure to stop by #thisweek:gnome.org with updates on your own projects!

August 17, 2023

To Conference Organisers Everywhere…

(well, not exactly everywhere …)

This is not an easy post for me to write, being a bit of a criticism / “you can do better” note for organisers of conferences that cater to a global community.

It’s not easy because most of the conferences I attend are community driven, and I have helped organise community conferences in the past. It is a thankless job, a labour of love, and you mostly do not get to enjoy the fruits of that labour as others do.

The problem is that these conferences end up inadvertently excluding members who live in, for lack of a better phrase, the Global South.

Visas

It always surprises me when I meet someone who doesn’t realise that I can’t just book tickets to go anywhere in the world. Not because this is information that everyone should be aware of, but because this is such a basic aspect of travel for someone like me. As a holder of an Indian passport, I need to apply for a visa to travel to … well most countries.

The list of countries that require a visa are clearly defined by post-colonial geopolitics, this is just a fact of life and not something I can do very much about.

Getting a Visa

Applying for a visa is a cumbersome and intrusive process that I am now used to. The process varies from country to country, but it’s usually something like:

  • Get an invitation letter from conference organisers
  • Book all the tickets and accommodation for the trip
  • Provide bank statements for 3-6 months, income tax returns for ~3 years (in India, those statements need attestation by the bank)
  • Maybe provide travel and employment history for the past few years (how many years depends on the country)
  • Get an appointment from the embassy of the country you’re traveling to (or their service provider)
  • Submit your passport and application
  • Maybe provide documentation that was not listed on the provider’s website
  • Wait for your passport with visa (if granted) to be mailed back to you

The duration of visa (that is how long you can stay in the country) depends on the country.

In the EU, I am usually granted a visa for the exact dates of travel (so there is no flexibility to change plans). The UK allows you to pay more for a longer visa.

The US and Canada grant multi-year visas that allow one to visit for up to 6 months by default (in the US, whether you are permitted to enter and how long you may stay are determined by the person at the border).

Timelines

Now we get to the crux of the problem: this process can take anywhere from a few days (if you are very lucky) to a few months (if you are not).

Appointments are granted by the embassy or the third party that countries delegate application collection to, and these may or may not be easily available. Post-pandemic, I’ve seen that several embassies just aren’t accepting visitor visa appointments or have a multi-month wait.

If you do get an appointment, the processing time can vary again. Sometimes, it’s a matter of a few days, sometimes a few weeks. A lot of countries I have applied to recommend submitting your application at least 6 weeks in advance (this is from the date of your visa appointment which might be several weeks in the future).

Conference Schedules

If you’re organising a conference, there are a few important dates:

  • When the conference dates are announced
  • When the call for participation goes out
  • When it ends
  • When speakers are notified
  • The conference itself

These dates are based on a set of complex factors — venue availability and confirmation, literally writing and publishing all the content of the website, paper committee availability, etc.

But if you’re in my position, you need at least 2-3 months between the first and the last step. If your attendance is conditional on speaking at the conference (for example, if your company will only sponsor you if you’re speaking), then you need a minimum of 2-3 months between when speakers are notified and the conference starts.

From what I see, this is not something that is top-of-mind for conference organisers. That may happen for a host of perfectly understandable reasons, but it also has a cost to the community and individuals who might want to participate.

Other Costs

Applying for a visa costs money. This can be anything from a few hundred to over a 1000 US dollars.

It also costs you time — filling in the application, getting all the documentation in place, getting a physical visa photo (must be no older than 6 months), traveling to an appointment, waiting in line, etc. This can easily be a matter of a day if not more.

Finally, there is an emotional cost to all this — there is constant uncertainty during the process, and a visa rejection means every visa you apply for thereafter needs you to document that rejection and reason. And you may find out just days before your planned travel whether you get to travel or not.

What Can One Do?

All of this clearly sucks, but the problem of visas is too big and messy for any of us to have any real impact on, at least in the short term. But if you’re organising a conference, and you want a diverse audience, here are a few things you can do:

  • Announce the dates of the conference as early as possible (allows participants to book travel, visa appointments, maybe club multiple conferences)
  • Provide invitation letters in a timely manner
  • Call for participation as early as possible
  • Notify speakers as soon as you can

I know of conferences that do some if not all of these things — you know who you are and you have my gratitude for it.

If you made it this far, thank you for reading.

GNOME 45 Core Apps Update

It’s been a few months since I last reviewed the state of GNOME core apps. For GNOME 45, we have implemented the changes proposed in the “Imminent Core App Changes” section of that blog post:

  • Loupe enters core as GNOME’s new image viewer app, developed by Christopher Davis and Sophie Herold. Loupe will be branded as Image Viewer and replaces Eye of GNOME, which will no longer use the Image Viewer branding. Eye of GNOME will continue to be maintained by Felix Riemann, and contributions are still welcome there.
  • Snapshot enters core as GNOME’s new camera app, developed by Maximiliano Sandoval and Jamie Murphy. Snapshot will be branded as Camera and replaces Cheese. Cheese will continue to be maintained by David King, and contributions are still welcome there.
  • GNOME Photos has been removed from core without replacement. This application could have been retained if more developers were interested in it, but we have made the decision to remove it due to lack of volunteers interested in maintaining it. Photos will likely be archived eventually, unless a new maintainer volunteers to save it.

GNOME 45 beta will be released imminently with the above changes. Testing the release and reporting bugs is much appreciated.

We are also looking for volunteers interested in helping implement future core app changes. Specifically, improvements are required for Music to remain in core, and improvements are required for Geary to enter core. We’re also not quite sure what to do with Contacts. If you’re interested in any of these projects, consider getting involved.

August 16, 2023

The least convenient way of defining an alpha value

PDF's drawing model inherits from PostScript, which was originally designed in the early 80s. It works and is really nice but the one glaring hole it has is missing transparency support. The original PDF spec from 1993 has no transparency support either, it was added in version 1.4 that came out mere 8 years later in 2001.

Setting drawing operation colours in PDF is fairly straightforward. You list the values you want and call the color setting command, like this:

1.0 0.1 0.4 rg    % Sets RGB nonstroke colour
0.2 0.8 0.0 0.2 K % Sets CMYK stroke colour

The property name for alpha in PDF is CA and ca for stroking and nonstroking operations, respectively. This would imply that you should be able to do this:

0.9 ca  % Sets nonstroke alpha? [no]
0.2 CA  % Sets stroke alpha?    [no]

But of course you can't. Instead the alpha value can only be set via a Graphics State dictionary, which is a top level PDF object that you then need to create, use, link to the current page's used resources and all that jazz.

/Alpha01State gs % Set graphics state to the dictionary with the given name

This works fine for use cases such as compositing layers that do not themselves use transparency. In that case you render the layer to a separate XObject, create a graphics dictionary with the given composition parameters, use it and then render the layer XObject. It does not work at all in pretty much any other case.

The more observant among you might have already noticed that you need to create a separate graphics state dictionary for every transparency value used in your document. If you are, say, a vector graphics application and allow artists to adjust the transparency for each object separately using a slider (as you should), then it means that you may easily have hundreds or thousands of different alpha values in your document. Which means that you need to create hundreds or thousands of different transparency graphics state dictionaries if you want to preserve document fidelity.

Why is it implemented like this?

As is common, we don't really know (or at least I don't). The Finger of Speculation points towards the most usual of suspects: backwards compatibility.

Suppose the alpha command had been added to the PDF spec. It would mean that trying to open documents with those commands in older PDF renderers would cause crashes due to unknown commands. Yes, even if the document had specified a newer minimum PDF version, because PDF renderers would try to open and show them anyway rather than popping an error message saying "Document version is too new". OTOH adding new entries to a graphics state dictionary is backwards compatible because the renderer would just ignore keys it does not understand. This renders the document as if no transparency had ever been defined. The output is not correct, but at least mostly correct. Sadly the same can't be done in the command stream because it is a structureless stream of tokens following The One True Way of RPN.

If the hypothesis above is true, then the inescapable conclusion is that no new commands can be added to the PDF command stream. Ever.

August 15, 2023

Damage areas across the VirtIO space

In the last few months, I have been trying to improve the default UI shipped by QEMU. As you might not know, QEMU ships with various UI backends: GTK, SDL, Cocoa and recently a DBus one.

I first started trying to port the GTK 3 backend to GTK 4 but faced some issues where I couldn't use GtkApplication as it starts its own GMainLoop which interferes with some god knows what internal GMainLoop started by QEMU itself. My intention was not to only do a simple port but also to see how we could optimize the rendering path as well.

At that time, I also learned that Christian Hergert started working on libmks, a new client-side C library of the DBus backend as he has the intention of using it in GNOME Builder. Marc-André Lureau, one of the upstream QEMU maintainers, is also working on something similar, with a larger scope and using Rust called RDW, a Remote Desktop Widget to rule them all.

Damage areas

From a rendering perspective, the major difference between libmks and RDW at that time is that libmks used a custom GdkPaintable doing a tiled rendering of a GLTexture imported from a DMABuf as GTK had no API to set the damaged region of a GLTexture and it was doing a pointer comparison for a GSK_TEXTURE_NODE render nodes, hence the usage of a tiled rendering. RDW on the other hand was using a plain GtkGLArea.

Christian Hergert also shared with Javier Martinez Canillas and me about his findings while working on libmks:

So now that we have fixed the VirtIO GPU driver and QEMU, we should be able to get proper damage reporting information from the guest, right??? Well, that is what Javier & I were hoping for. A few days into debugging what was going on without much luck, I met Robert Mader during Linux App Summit and discussed the issue with him and he mentioned a certain deny list of drivers from using the atomic KMS API in Mutter. Interesting.

The Monday after LAS, I shared the info with Javier and we found out the root cause of our pain. As the atomic KMS API has no properties for setting the cursor hotspot, which is useful for the virtualized use case, it was put on a deny list until such properties are added to the kernel. Javier also found that Zack Rusin from VMware is already on it, he submitted a patch for the kernel and had a local branch for Mutter as well. Albert Esteve tested the Kernel/Mutter patches and confirmed they were good. He also wrote an IGT test, which will hopefully help get the patch merged soon.

Around that time, Benjamin Otte was working on adding a new builder-like API to simplify the creation of a GdkGLTexture, with a very neat feature, allowing to set the exact damage region instead of doing a pointer comparison. I offered to test the API and ported libmks to use it.

Other improvements

Sergio Lopez Pascual worked on adding multi-touch support to virtio-input/QEMU which I then exposed on the QEMU DBus interface & implemented the libmks side of it.

For the future

  • We still don't have all the required features to make libmks a drop-in replacement for Spice usage in GNOME Boxes like USB redirection, Clipboard sharing. I have WIP branches for both, one day I will finish them.
  • Porting GNOME Boxes to GTK 4
  • The Kernel / Mutter patches from Zack being merged

Once all these pieces land, you will hopefully be able to enjoy a slightly better experience when running a virtualized OS that properly propagates the damaged regions.

For now, you can try libmks either by using the debugging tool shipped with the library or by using this very small client I wrote on top of it, Snowglobe.

Acknowledgement

As you can tell, a bunch of components and people were involved in this few months trip. I would like to thank everyone for taking the time to explain basic things to me as well as helping out to get this done! You can now enjoy a well deserved screen recording showing the implication of these changes

August 14, 2023

Librsvg is available from crates.io now

Since last Friday, librsvg is available from crates.io. You can add this line to your dependencies in Cargo.toml:

librsvg = "2.57.0-beta.2"

I am nailing down the release process for this, but my intention is that starting with GNOME 45 / librsvg-2.57.0, the Rust crate will be in sync with the GNOME release schedule: a stable minor release every six months, and micro releases regularly and as needed.

For now, these releases are equivalent and contain the same code:

  • 2.56.92 (the usual tarball, beta release before GNOME 45)
  • 2.57.0-beta.2 (release for crates.io)

Both git tags exist in gitlab.gnome.org's repository and point to the same commit.

While GNOME uses .9x micro version numbers to identify alpha or beta releases, Rust uses Semantic Versioning, which allows for the -beta.2 suffixes. I'll keep the corresponding schemes and add both git tags to the release commits.

New responsibilities

As part of the same process outlined in Matthias Clasen's "LibreOffice packages" email, my management chain has made the decision to stop all upstream and downstream work on desktop Bluetooth, multimedia applications (namely totem, rhythmbox and sound-juicer) and libfprint/fprintd. The rest of my upstream and downstream work will be reassigned depending on Red Hat's own priorities (see below), as I am transferred to another team that deals with one of a list of Red Hat’s priority projects.

I'm very disappointed, because those particular projects were already starved for resources: I spent less than 10% of my work time on them in the past year, with other projects and responsibilities taking most of my time.

This means that, in the medium-term at least, all those GNOME projects will go without a maintainer, reviewer, or triager:
- gnome-bluetooth (including Settings panel and gnome-shell integration)
- totem, totem-pl-parser, gom
- libgnome-volume-control
- libgudev
- geocode-glib
- gvfs AFC backend

Those freedesktop projects will be archived until further notice:
- power-profiles-daemon
- switcheroo-control
- iio-sensor-proxy
- low-memory-monitor

I will not be available for reviewing libfprint/fprintd, upower, grilo/grilo-plugins, gnome-desktop thumbnailer sandboxing patches, or any work related to XDG specifications.

Kernel work, reviews and maintenance, including recent work on SteelSeries headset and Logitech devices kernel drivers, USB revoke for Flatpak Portal support, or core USB is suspended until further notice.

All my Fedora packages were orphaned about a month and a half ago, it's likely that there are still some that are orphaned, if there are takers. RHEL packages were unassigned about 3 weeks ago, they've been reassigned since then, so I cannot point to the new maintainer(s).

If you are a partner, or a customer, I would recommend that you get in touch with your Red Hat contacts to figure out what the plan is going forward for the projects you might be involved with.

If you are a colleague that will take on all or part of the 90% of the work that's not being stopped, or a community member that was relying on my work to further advance your own projects, get in touch, I'll do my best to accommodate your queries, time permitting.

I'll try to make sure to update this post, or create a new one if and when any of the above changes.

August 11, 2023

GUADEC 2023

I was lucky enough to attend the 2023 edition of GUADEC in Riga, Latvia.

Julian, Philip, Georges, Pedro and Marco at the GUADEC welcome event.

In a way GUADEC is more fun each year because each time I know more people, have more things to talk about, and also it’s 4 years since my last in-person GNOME event so this one was particularly special.

Where else can you have in-depth conversations about the future of GObject, for example? (The future sounds interesting, by the way).

Emmanuele presenting a slide that reads "GObject" with a very surprised guy

My main focus was getting everyone interested in end-to-end testing using openQA, which met with quite some success, although there is still some way to go before we can declare the end-to-end testing “production ready”. I will keep working on the remaining tasks in my 1-2 hrs a week that I am sometimes able to spend on it; as always, things will go much faster if and when more folk get involved in the effort. We are in #gnome-os if you want to join in.

You can watch my talk about end-to-end testing with openQA here.

We had a BoF session about desktop search, which was one of the most useful discussions I remember having on this topic, we covered topics all across the GNOME desktop rather than focusing on specific components, breaking free for once from Conway’s Law. I wrote up some of the outcomes here. It’s a good time to get involved in search!

After GUADEC was done I sailed from Liepaja to Lübeck, then had some vacation time around Germany and Italy, including a memorable gig watching the Slackers near lake Garda, finishing at midnight and somehow arriving back to Santiago about 10 hours later via various car, plane, train, etc. Luckily we missed most of the heatwave during the trip. I am only now getting back to work.

Train bridge in Riga

Many thanks to the local team who did a great job organising the event (I know first-hand how difficult it is :-), and good luck to Cassidy and the team organising GUADEC 2024 in Denver, USA.

It’s too early to know if I’ll make it to Denver but it’s only fair that us Europeans sometimes do a bit of long haul travelling and dealing with hostile border control authorities, stuff that some GUADEC attendees have to do every single year 🙂

August 09, 2023

Please help test (and fix) GTG’s GTK 4 port

As you know, even with a “simple” language like Python, porting a desktop application to a new version of GTK can be a pretty significant amount of work; doubly so when it is accompanied by major refactoring of the app itself at the same time.

In Getting Things GNOME‘s case, this has been over a year in the making, for various technical and personal/life reasons.

We need your help to determine when/if it would be “safe” to merge our long-running core rewrite & GTK 4 port branch to GTG‘s main branch and repository. We think it mostly works, but we can’t be sure until you try it out (preferrably while using a copy of your real world data file).

If you have an up-to-date Linux distro with recent versions of GTK (ex: 4.10.4+), and would like to help test & fix the upcoming GTK 4 version of GTG, check out this branch / PR and give it a try in the coming days. You can then help by:

  • Finding and reporting remaining showstopper issues: problems specific to that branch should be reported here (in Diego’s repository) for now.
    • We’re looking for problems that would be considered unacceptable/non-functional for the sake of merging to the main repository. Especially, anything related to data or dangerously broken behavior, if any.
    • Broken plugins are acceptable at this point.
    • If you can’t find any unreported issues after extensive testing… let us know too, it’s nice to know our code is perfect 😉
  • Sending merge requests to Diego’s repository (check the issues and existing PRs, if any, to avoid duplicate work) would help a ton, of course, because we’ve been stretched very thin lately.

Being able to confidently merge this branch would make it much easier for others in the GTG community to have a stable base to work on, and provide the remaining fixes to bring version 0.7 to the finish line.


P.s.: remember that we also have a Matrix discussion channel (see the GTG wiki page for details) if you’d like to coordinate some aspects in a more informal and interactive manner before posting to GitHub.

August 08, 2023

a negative result

Update: I am delighted to have been wrong! See the end.

Briefly, an interesting negative result: consider benchmarks b1, b2, b3 and so on, with associated .c and .h files. Consider libraries p and q, with their .c and .h files. You want to run each benchmark against each library.

P and Q implement the same API, but they have different ABI: you need to separately compile each benchmark for each library. You also need to separate compile each library for each benchmark, because p.c also uses an abstract API implemented by b1.h, b2.h, and so on.

The problem: can you implement a short GNU Makefile that produces executables b1.p, b1.q, b2.p, b2.q, and so on?

The answer would appear to be "no".

You might think that with call and all the other functions available to you, that surely this could be done, and indeed it's easy to take the cross product of two lists. But what we need are new rules, not just new text or variables, and you can't programmatically create rules. So we have to look at rules to see what facilities are available.

Consider the rules for one target:

b1.p.lib.o: p.c
	$(CC) -o $@ -include b1.h $<
b1.p.bench.o: b1.c
	$(CC) -o $@ -include p.h $<
b1.p: b1.p.lib.o b1.p.bench.o
    $(CC) -o $@ $<

With pattern rules, you can easily modify these rules to parameterize either over benchmark or over library, but not both. What you want is something like:

*.%.lib.o: %.c
	$(CC) -o $@ -include $(call extract_bench,$@) $<
%.*.bench.o: %.c
	$(CC) -o $@ -include $(call extract_lib,$@) $<
%: %.lib.o %.bench.o
	$(CC) -o $@ $<

But that doesn't work: you can't have a wildcard (*) in the pattern rule. (Really you would like to be able to match multiple patterns, but the above is the closest thing I can think of to what make has.)

Static pattern rules don't help: they are like pattern rules, but more precise as they apply only to a specific set of targets.

You might think that you could use $* or other special variables on the right-hand side of a pattern rule, but that's not the case.

You might think that secondary expansion might help you, but then you open the door to an annoying set of problems: sure, you can mix variable uses that are intended to be expanded once with those to be expanded twice, but the former set better be idempotent upon second expansion, or things will go weird!

Perhaps the best chance for a make-only solution would be to recurse on generated makefiles, but that seems to be quite beyond the pale.

To be concrete, I run into this case when benchmarking Whippet: there are some number of benchmarks, and some number of collector configurations. Benchmark code will inline code from collectors, from their header files; and collectors will inline code from benchmarks, to implement the trace-all-the-edges functionality.

So, with Whippet I am left with the strange conclusion that the only reasonable thing is to generate the Makefile with a little custom generator, or at least generate the part of it to do this benchmark-library cross product. It's hard to be certain about negative results with make; perhaps there is a trick. If so, do let me know!

epilogue

Thanks to a kind note from Alexander Monakov, I am very happy to be proven wrong.

See, I thought that make functions were only really good in variables and rules and the like, and couldn't be usefully invoked "at the top level", to define new rules. But that's not the case! eval in particular can define new rules.

So a solution with eval might look something like this:

BENCHMARKS=b1 b2 b3
LIBS=p q

define template
$(1).$(2).lib.o: $(2).c
	$$(CC) -o $$@ -include $(1).h $$<
$(1).$(2).bench.o: $(1).c
	$$(CC) -o $$@ -include $(2).h $$<
$(1).$(2): $(1).$(2).lib.o $(1).$(2).bench.o
    $$(CC) -o $$@ $$<
end

$(foreach BENCHMARK,$(BENCHMARKS),\
  $(foreach LIB,$(LIBS),\
     $(eval $(call template,$(BENCHMARK),$(LIB)))))

Thank you, Alexander!

Updating Fedora the unsupported way

I dug out a computer running Fedora 28, which was released 2018-04-01 - over 5 years ago. Backing up the data and re-installing seemed tedious, but the current version of Fedora is 38, and while Fedora supports updates from N to N+2 that was still going to be 5 separate upgrades. That seemed tedious, so I figured I'd just try to do an update from 28 directly to 38. This is, obviously, extremely unsupported, but what could possibly go wrong?

Running sudo dnf system-upgrade download --releasever=38 didn't successfully resolve dependencies, but sudo dnf system-upgrade download --releasever=38 --allowerasing passed and dnf started downloading 6GB of packages. And then promptly failed, since I didn't have any of the relevant signing keys. So I downloaded the fedora-gpg-keys package from F38 by hand and tried to install it, and got a signature hdr data: BAD, no. of bytes(88084) out of range error. It turns out that rpm doesn't handle cases where the signature header is larger than a few K, and RPMs from modern versions of Fedora. The obvious fix would be to install a newer version of rpm, but that wouldn't be easy without upgrading the rest of the system as well - or, alternatively, downloading a bunch of build depends and building it. Given that I'm already doing all of this in the worst way possible, let's do something different.

The relevant code in the hdrblobRead function of rpm's lib/header.c is:

int32_t il_max = HEADER_TAGS_MAX;
int32_t dl_max = HEADER_DATA_MAX;

if (regionTag == RPMTAG_HEADERSIGNATURES) {
il_max = 32;
dl_max = 8192;
}

which indicates that if the header in question is RPMTAG_HEADERSIGNATURES, it sets more restrictive limits on the size (no, I don't know why). So I installed rpm-libs-debuginfo, ran gdb against librpm.so.8, loaded the symbol file, and then did disassemble hdrblobRead. The relevant chunk ends up being:

0x000000000001bc81 <+81>: cmp $0x3e,%ebx
0x000000000001bc84 <+84>: mov $0xfffffff,%ecx
0x000000000001bc89 <+89>: mov $0x2000,%eax
0x000000000001bc8e <+94>: mov %r12,%rdi
0x000000000001bc91 <+97>: cmovne %ecx,%eax

which is basically "If ebx is not 0x3e, set eax to 0xffffffff - otherwise, set it to 0x2000". RPMTAG_HEADERSIGNATURES is 62, which is 0x3e, so I just opened librpm.so.8 in hexedit, went to byte 0x1bc81, and replaced 0x3e with 0xfe (an arbitrary invalid value). This has the effect of skipping the if (regionTag == RPMTAG_HEADERSIGNATURES) code and so using the default limits even if the header section in question is the signatures. And with that one byte modification, rpm from F28 would suddenly install the fedora-gpg-keys package from F38. Success!

But short-lived. dnf now believed packages had valid signatures, but sadly there were still issues. A bunch of packages in F38 had files that conflicted with packages in F28. These were largely Python 3 packages that conflicted with Python 2 packages from F28 - jumping this many releases meant that a bunch of explicit replaces and the like no longer existed. The easiest way to solve this was simply to uninstall python 2 before upgrading, and avoiding the entire transition. Another issue was that some data files had moved from libxcrypt-common to libxcrypt, and removing libxcrypt-common would remove libxcrypt and a bunch of important things that depended on it (like, for instance, systemd). So I built a fake empty package that provided libxcrypt-common and removed the actual package. Surely everything would work now?

Ha no. The final obstacle was that several packages depended on rpmlib(CaretInVersions), and building another fake package that provided that didn't work. I shouted into the void and Bill Nottingham answered - rpmlib dependencies are synthesised by rpm itself, indicating that it has the ability to handle extensions that specific packages are making use of. This made things harder, since the list is hard-coded in the binary. But since I'm already committing crimes against humanity with a hex editor, why not go further? Back to editing librpm.so.8 and finding the list of rpmlib() dependencies it provides. There were a bunch, but I couldn't really extend the list. What I could do is overwrite existing entries. I tried this a few times but (unsurprisingly) broke other things since packages depended on the feature I'd overwritten. Finally, I rewrote rpmlib(ExplicitPackageProvide) to rpmlib(CaretInVersions) (adding an extra '\0' at the end of it to deal with it being shorter than the original string) and apparently nothing I wanted to install depended on rpmlib(ExplicitPackageProvide) because dnf finished its transaction checks and prompted me to reboot to perform the update. So, I did.

And about an hour later, it rebooted and gave me a whole bunch of errors due to the fact that dbus never got started. A bit of digging revealed that I had no /etc/systemd/system/dbus.service, a symlink that was presumably introduced at some point between F28 and F38 but which didn't get automatically added in my case because well who knows. That was literally the only thing I needed to fix up after the upgrade, and on the next reboot I was presented with a gdm prompt and had a fully functional F38 machine.

You should not do this. I should not do this. This was a terrible idea. Any situation where you're binary patching your package manager to get it to let you do something is obviously a bad situation. And with hindsight performing 5 independent upgrades might have been faster. But that would have just involved me typing the same thing 5 times, while this way I learned something. And what I learned is "Terrible ideas sometimes work and so you should definitely act upon them rather than doing the sensible thing", so like I said, you should not do this in case you learn the same lesson.

comment count unavailable comments

August 07, 2023

GUADEC 2023

I attended GUADEC 2023 this year in-person in Rīga. It was nice to be able to see more people in person than last year’s mini-GUADEC in Berlin, and nice to be able to travel overland/oversea to Rīga, avoiding flights and the associated huge carbon emissions. Thank you to the GNOME Foundation and my employer, Endless, for covering the travel.
And a big thank you to the local event organising team, the AV team, the volunteers and the Foundation staff for making it all happen.

The quality of the talks this year was really high. I don’t think there was a single talk slot I skipped. As a result, I didn’t get much hacking done! But there were good hallway conversations and catch ups.

I gave two talks, one on some simple improvements people can make to their apps to reduce internet data use and power use when doing so would be beneficial to the user (when on a metered network or in power-saver mode).
The aim was to remind people how easy it is to do this, and provide some examples of how different apps present these states/events in the UI, since the best way to do that can differ between apps.

You can find the slides to that talk here (notes here), and a video of it is on YouTube.

The talk has resulted in three GNOME initiatives. Each corresponds to a different state your app can pay attention to — some apps should pay attention to all of them, some apps only some of them. Please take 10 minutes to
check your app against the initiatives and make updates if necessary. I’m in the gnome-hackers and gnome-circle Matrix rooms if you have any questions about them.

My second talk was an overview of work I’ve been doing on-and-off over the past couple of years (based on work by others) to allow apps to save and restore their state when the user logs out or shuts down the computer. The idea is that the user can restore
the set of apps they were using, in the state and configuration they were left in, when next starting the computer or logging in. It’s a big project, and there’s a long way to go, but it felt like the right time to present what
we’ve got so far, let more people know how the project is structured, and get feedback from toolkit developers and app authors about the whole idea.

You can find the slides to that talk here (notes here), and a video of it is also on YouTube.

Thankfully there was 5 minutes for questions at the end of the talk, and people used them well to raise some good points and ask some good questions. I’m still in the process of factoring all that feedback into the plan, but
should hopefully have an update to give on the project in a future blog post soon.

Interesting talks I attended included Peter Hutterer’s talk about sending and capturing input in Wayland, which I think distilled his blogposts about the topic very clearly.
Allan Day’s talk about communication, which was an excellent summary of a rather long list of books on the subject, and it was very clearly presented. I feel like I could do with a cheatsheet list of his recommendations to stick next to my computer sometimes.
Evan Welsh, Philip Chimento, Nasah Kuma and Sonny Piers’s talks about JavaScript, TypeScript and the latest changes in GJS. These provided a good update on a lot that has been happening in GJS, a useful overview of TypeScript, and were really clearly presented.
Jussi Pakkanen’s talk about circles, or was it about settings? It was a good example of a lightning talk which draws you in then spins you around.

In the hallway track, I had some interesting chats with Bartłomiej about the infrastructure of ODRS, which is used to provide ratings/review data to gnome-software.
I also had some positive conversation with Cassidy about plans for GUADEC 2024 in Denver.
And at the event dinner, a really energising chat with Scott about canyoning, hiking, skiing and caving in the US and the UK.

August 06, 2023

I’m back from GUADEC 2023

I’m back from Rīga, Latvia where I attended GUADEC 2023—the annual GNOME conference. It was my first time attending since 2019 due to COVID-19 plus having had another child in 2022. It simultaneously felt like it had been ages since seeing everyone, and like we picked back up after no time at all.

Old building

For me, attending GUADEC in person is exceptionally important: the face-to-face conversations, exploration of the host city, eating and drinking together, and spontaneous hacking all serve as powerful bonding experiences and help develop mutual understandings of the actual humans behind online usernames and avatars. We come away from GUADEC with fresh ideas, new perspectives, stronger friendships, and renewed energy to make GNOME and the open technologies we all care about better than before. You can get a small fraction of that from remote attendance—and remote participation is important to make the event as accessible and inclusive as possible—but I greatly prefer in-person participation if at all possible.

While traveling from the US was as long and grueling as ever (and meant nearly two weeks away from my family), it was ultimately worth it to be in person. I just hope we can spread GUADEC out a bit more in coming years so it’s not this hard every year. 😅 More on that in a bit…

GNOME Advisory Board

I was given the opportunity to attend the GNOME Advisory Board meeting for the first time, representing Endless. The Advisory Board is a group of organizations involved in and with a vested interest in the success of GNOME that meets twice annually—at GUADEC and FOSDEM. There is no decision making authority, but it serves as a time for people from these organizations to get together with one another and the elected GNOME Foundation Board of Directors to discuss their ongoing and upcoming plans as they relate to GNOME.

There is a shared understanding of both collaboration and confidentiality—we share with one another what we can to coordinate our efforts, ultimately multiplying our individual organizations’ impact. Because of the understanding of confidentiality, I won’t go into detail; but here were common threads and conversations:

Accessibility

Multiple people mentioned investing efforts into improving accessibility of GNOME technologies. Platform modernization with Wayland and GTK4 brought troves of technical improvements, but the resources for tackling accessibility to ensure it remains as good as or better than in the past have been lacking in recent years. This has been recognized and I’m confident it will be well-addressed.

In addition to resources provided by organizations, there may be funding from grants to help in these areas in the coming years. If you are someone who is aware of grant application processes or knows of specific grant programs, this is a huge way you can contribute to GNOME; reach out to the Foundation to help coordinate securing additional funding in this area. Or contact me, and I can point you in the right direction—my contact details are in the footer!

Member Participation

Due to a variety of reasons, the Advisory Board meeting was less-attended this year than in the past. We discussed this a bit—particularly over the lunch hour—and brainstormed ways to ensure everyone can be represented. One area identified was that the move from mailing lists to Discourse and GitLab may have left some folks behind, and this will be addressed. We also identified outdated contact details and planned to speak to specific individuals to help ensure member organizations knew of the meeting and knew their attendance was welcome and encouraged.

We also chatted a bit—both at the meeting and throughout the week—about how we could encourage other organizations to join the Advisory Board to represent diverse interests. This inspired me to sign up for a “What is the GNOME Advisory Board?” lightning talk as a first step towards demystifying the Advisory Board—and encouraging more organizations to join.

Travel Sponsorship & Hackfest Locations

A member of one organization reported that they were able to help GNOME by providing physical space at their organization’s offices for hackfests, which is another great way organizations can contribute to GNOME; travel is one aspect of organizing meetups, but having a well-resourced location is important as well. If you work for an organization that could help out in this area as well, let the Foundation know—or contact me via the details in the footer and I can forward it on.

There was also feedback about the travel sponsorship process; this was an area the Foundation knew was a bit painful this year as things moved to GitLab, and some outdated information persisted in other places. But it’s being addressed.

Reproducibility of Flatpaks

We heard an update from Flathub as well as at least one member organization that there are mutual interests in getting reproducible Flatpak builds integrated with Flathub.

I’m not deeply into the enterprise world, so forgive me if I don’t quite get this right, but; my understanding of one example was that if an enterprise distribution wants to encourage their customers to use Flathub because it’s where the apps are, they should be able to sign off on a hash of the app build built on their own infrastructure—even if the customer ends up downloading that build from Flathub’s infrastructure. If the apps are built reproducibly, the hashes would match, so it wouldn’t matter where the bits themselves came from.

I know this is an important step in the overall trust model of a neutral infrastructure like Flathub, anyway; you should be able to trust the middleware not because of the people who are involved (and they’re good people!), but because it’s entirely transparent and auditable.

Upstreaming More than Code

Something raised was: how can the Foundation and Advisory Board organizations better support upstreaming more than just code contributions—that is, things like metrics, survey results, user study results, and rationale behind design decisions? For example, some downstream distributions make design decisions that aren’t upstreamed into GNOME (think: adding a dock, changing stylesheets, etc.). While downstream differentiation is important to those organizations, are there ways we can better support that or learn from those changes upstream to make GNOME a better product for all the downstreams?

This was more of a “get the gears turning” discussion without any specific immediate actions or outcomes, but it was an interesting conversation nonetheless.

My Talks

I originally planned to give one talk this year: an updated and more GNOME-specific version of a talk I gave last year at the Ubuntu Summit. Since I was involved with the GNOME Design team over the past year, I also ended up volunteering to co-present the annual GNOME Design State of the Union talk, then I signed up for the aforementioned What is the GNOME Advisory Board lightning talk—and then I was almost roped into co-presenting another talk but ended up just helping write and prepare slides for it. 😅️

I actually really enjoyed being so involved this year, and I regret not getting signed up to help with introducing speakers, A/V, or some other volunteer position. Maybe I’ll make up for it next year…

How to Make a Delightful App Listing

This was a fun talk, but I committed one of the conference sins I always try to avoid: I rewrote much of it the night before. 😬️ It was honestly not that bad since I’d presented a version of it last year, but it turns out that Flathub had since published really great AppData guidelines—and I only learned of this at a bar the night before my talk. So… I rewrote the last section of my talk incorporating some of that advice.

In the end, I think it made the talk far better. It was a good taster of the guidelines and I hope it encouraged app developers—both of core GNOME apps, GNOME Circle apps, and the broader ecosystem—to revisit their app listings to present them in the best light possible.

I’ve published the slides, speaker notes, and a link to the stream.

GNOME Design: State of the Union

The annual update from the GNOME Design team was fun to participate in. While I wasn’t deeply involved in making mockups, icons, or major design decisions, I was involved nearly every week over the past year in providing feedback and insight. We divided the talk into a six sections plus an intro and summary, with us each presenting two of those. I took the Intro and Core Apps; while they were brief, it was great to be able to share the stage with (and show off the work of) talented folks constantly pushing GNOME design forward.

Here are the slides and a link to the stream.

What is the GNOME Advisory Board?

This was the lightning talk I decided to put on the schedule after attending my first GNOME Advisory Board meeting. It was pretty rapid-fire, and most of the actual content came straight from the GNOME wiki, but I got a lot of feedback afterwards that it helped demystify the Advisory Board—and even inspired a few people to reach out to organizations to encourage them to join. 🙌️

I’ve published the slides and a link to the stream.

New Users for GNOME

This was a talk by Robert McQueen, CEO of the Endless OS Foundation and President of the GNOME Foundation Board of Directors—but Will Thompson and I did help out a bit in preparing notes and some slides. 😁️

Rob pulled it all together into great talk sharing insights, knowledge, and ways forward learned by our work at Endless—particularly around how first-time computer users (including both kids and adults) are an impactful target audience for the work we do on GNOME.

YouTube Stream

Hallway Chats

A huge part of the GUADEC experience for me is the “hallway” chats often happening literally in hallways, but also over lunch, drinks, or late nights in someone’s accommodation.

GUADEC 2024: Denver, CO, USA 🎉️

A persistent focus of mine was on GUADEC itself: how it was organized, how people “did” GUADEC, and what their feedback was. This was because—as was announced in the closing ceremony—GUADEC is coming to my home town of Denver, Colorado in 2024! I think I kept the lid on it until the closing ceremony, but I made sure to pay attention people to and listen to their input.

One tiny example of feedback: yoga mats! Someone suggested that with all the travel, walking, lugging backpacks, and late nights, it would be nice to have a quiet corner somewhere with half a dozen or more yoga mats and sanitizing spray. Attendees could take a moment to stretch out or just center themselves during a break. I thought this was a great idea, and I’ll be pushing for this in Denver—maybe it will become a recurring part of GUADEC?

We also talked a lot about how to document things clearly on the GUADEC website, reducing the duplicated GUADEC communication channels (I think I counted five different channels on Matrix… and most people only used one), encouraging inclusive eating options like vegetarian, vegan, and gluten-free restaurants, and of course: all the fun things people would want to do in and around Denver (Casa Bonita? Meow Wolf? The mountains?).

Another area we focused on was climate impact: while flying a bunch of Europeans to the US obviously has its impacts, there will also be significantly reduced travel for many of us within North, Central, and South America. Even so, I’d like to encourage lower-carbon transit options as much as possible, e.g. rail and bus if possible within the US, trying to keep everything for the week of GUADEC itself along convenient public transit or walkable, and reducing our impact in smaller but meaningful ways like encouraging reusing lanyards and avoiding other single-use waste. Philip Withnall provided some great tips for me, and I’ll be taking his advice for how to approach some of these areas.

The Can of Worms: Metrics

Against my better judgment (and the advice of friends), I broached the topic of privacy-preserving metrics once or twice… In all honesty, GUADEC hallways/bars are the perfect sort of place to have these conversations without the pressure of forming an Internet Opinion™️ etched forever into the collective memory. We’ve regularly been having these sorts of conversations since at least 2018, anyway, and while recent Fedora Discourse threads of doom have made it more of “a thing” than in years past, I felt it was important to speak candidly about it in person.

I came away with a few insights.

First, there are real areas where GNOME maintainers literally don’t know if complex code paths are ever touched in the real world, and this is an area a privacy-preserving tally-counter could provide real value to users; for example, GNOME Document Viewer (Evince) apparently handles something like ten more file formats than just PDFs, but nobody we talked to at a GNOME developer conference even knew that. Document Viewer is also one of the handful of apps that hasn’t been ported to GTK4 and Libadwaita, in part due to the complexity of maintaining those codepaths. It sure would be beneficial to the maintainers to know if anyone actually uses those other formats in Document Viewer, and if not, it could greatly speed up the modernization of the app—bringing both consistent design and improved performance—by simplifying what it supports. At the same time, if it turns out that one or two other format than PDF is actually widely used in the wild, then that could inform the design of Document Viewer as well, making it better at handling the tasks it’s actually used for.

Second, people still don’t really know the details of the open source metrics system developed by Endless and proposed to be used by Fedora. For example, the system protects privacy by reducing precision on the client side while still answering broad topics, e.g. by filing off the last several digits of latitude and longitude in estimated geolocations. It can aggregate data points locally and only submit a summary of events—e.g. an aggregate count of how many times a specific feature was used over the past week rather than submitting a timestamped event every time the feature was used. This was well received, but it was also clear that it could be better communicated since people didn’t know it.

Third, the idea of further “fuzzing” data via differential privacy or randomized response (techniques from at least as far back as the 1960s to collect aggregate information while protecting individual privacy) was very popular. While the aforementioned reduction in precision is already implemented, this sort of randomized response to ensure individual event deniability is not yet—this is a clear area that could improve the system for all users.

Lastly, people seem to be scared to raise their (or affiliated organizations’) heads over the parapet to become a target of The Internet Mob. It’s understandable considering the difficulty in communicating nuance plus the knee-jerk backlash from people around the Fedora proposal. However, I think this is actually a downstream issue from the two above: if people don’t know how this works and it doesn’t handle privacy preservation in some ways as thoroughly as say, Apple (even if it does so in a much more transparent and auditable way), then it sounds easier to just avoid it. Perhaps more energy could be spent around addressing those areas—in the end, GNOME contributors are some of the most privacy-conscious people and are exactly the type of people I want designing and developing anything to do with metrics, but that’s a point that seems to be lost on the Internet at large.

Guerrilla User Testing

An entertaining and at least mildly insightful task at GUADEC was cornering unsuspecting locals and getting them to try GNOME. Okay, it wasn’t quite like that: we did find friendly faces at coffee shops and around town who were willing to take a few moments to run through some impromptu user testing around the Activities button and the new proposed design. This included locals, staff of venues, and attendees’ partners who are not currently GNOME users.

I think there will be more shared on this process in the future, but my take-away was that the informal nature of this served as a sort of vibe check more than anything; we weren’t disciplined in how we took notes, what leading questions we asked or avoided, and how we presented it from person to person. Allan Day did write up a great more formal process that I’d like us to follow to get better results from, though.

Stars & Thumbs: Ratings & Reviews

The topic of ratings and reviews (e.g. in GNOME Software) came up a few times, inspired from feedback from the Bottles developers, attendees of my App Listing talk, and other hallway chats. I’ve summarized my thoughts in a blog post published last week; give it a read!

Summary & Thanks

I originally set out to detail every talk I attended, each social venue, etc. but there was so much to talk about around the “hallway” track that I wanted to detail that, instead. I would highly recommend taking a look at the YouTube Playlist of talks: they were all professionally recorded and streamed, and a gracious commentor has provided timestamps to each talk. 🙏️

I hope this write-up provided some insight into the goings-on of GUADEC and my experience there.

I’d like to thank my employer, the Endless OS Foundation, for allowing me to spend an entire week of work at GUADEC; it’s an incredibly valuable aspect of GNOME, and I love that Endless sees that. I’d also like to thank the GNOME Foundation for partially sponsoring my attendance as a member of the GNOME Foundation; ensuring people can get to these events is crucial for community building and all of the high-bandwidth communication that can really only happen in person.

Sponsored by GNOME Foundation