February 05, 2023

Implementing i18n-format, a Rust procedural macro

This will relate my short learning journey in implementing a procedural macro in Rust: i18n-format.

The problem

Building Rust applications means having to deal with localization, ie allowing the UI to be translated in other languages. This is a big deal for users, and thanks to existing tooling, if you use gtk-rs and if you use the rust-gtk-template to boostrap your project you should be all set. The GNOME community has an history of caring about this and most of the infrastructure is here.

However there is one sore point into this: gettext, the standard package used to handle strings localization at runtime doesn't support Rust. For some reason the patch to fix it is stuck in review.

While it mostly works, the lack of support cause one thing: the getttext! macro that allow the use of localized strings as format is ignored by the xgettext command used to extract and strings and generate the PO template. It is the !, a part of the macro invocation syntax in Rust, that is the blocker. Another problem is that in Rust, for safety reasons, you can only pass a string literal to format!, which is why gettext! exists in the first place.

Until xgettext is fixed, either these strings are missed in the localization process, or you end up reimplementing it with less flexibility. That's what some applications do.

What if I implement a macro that would allow using a function syntax and still use the macro underneath? Oh boy, that mean I need to implement a procedural macro1, something I have been dreading because it looks like it has a steep learning curve. It's a topic so complicated that it is glanced over by most books about Rust. Turns out it wasn't that difficult, thanks to a great tooling.

The idea is as follow: convince xgettext to recognize the string. It should look like a plain function, while we need to call a macro.

Macros

Rust has two type of macros. Declarative macros that replace a pattern with another. Not disimilar with C macros but with hygiene and parameter checking, declarative macros are very powerful. Unlike C macros, Rust macros are processed at the same time as the rest of the code, not with a pre-processor. You can't use them to rewrite keywords.2 A declarative macro call is marked with a ! and that's the source of our problem.

The second type is procedural macros. They are the type we are interested in. Procedural macros run code written in Rust, at compile time and allow (re)writing code. Procedural macros come in three flavours.

  • Derive macros, allow using the #[derive()] atrtribute, to generate code related to a specific type. One notable example of derive macro is the crate serde that uses it to automatically generate serialization and deserialization for types.

  • Attribute macros, allow tagging an attribute #[attribute] (it can have other parameters) to apply the macro to a specific item.

  • Function like macros, that are called like declarative macros allow generating code procedurally.

Lets put aside derive macros, as we are not doing this.

Reading

Some articles I read prior to doing this:

Cargo L Helper

Let's start by some preparation for working with procedural macros.

First, a procedural macro is implemented in its own crate, so you'll need to create one, even within a bigger project. You can group several procedural macros together, but you can't have anything else. The Cargo.toml needs to have:

[lib]
proc-macro = true

Second, debugging macro code is not always simple. Just install cargo-expand. This tool allow expanding the macro so you can review what is being generated. It's not disimilar to running the C preprocessor.

The can run cargo expand --test to expand your tests. Testing a macro is done in separate tests in the tests directory as you can't run the tests from inside the crate, and without a bigger effort is mostly limited to expanding the macro.

Note: for cargo expand you need to have the Rust nightly compiler toolchain installed.

First try

Let's see how we could solve the problem with an attribute macro. Let's name our macro i18n_fmt. As an attribute macro it could work this way:

#[i18n_fmt]
let s = i18n_fmt("My string is {}", value);

The macro called by the attribute would parse the statement and replace i18n_fmt with gettext!.

But this doesn't work. Attributes applied to statements are not supported (yet). You can apply them to a type, a function, a module, a type field (struct or enum). Too bad this looked like it could work. Trying to implement it, will be left as an exercise to the reader. All you need is to stub the macro implementation to be a no-op and as you try to use it in your testing code, the compiler will complain.

Second try

Let's see with a function-like macro. The macro would, inside its block, replace the function call with a macro call. This could work like that:

let s = i18n_fmt! {
    i18n_fmt("My string is {}", value)
}

The procedural macro function is i18n_fmt! and inside the block will replace i18n_fmt by gettext!. This should work. xgettext with the keyword i18n_fmt would find the string and add it to the po template. Note that here i18n_fmt isn't a function, it isn't even defined. Without the macro work compilaton would fail. It could be literally any other name, but let's be reasonable on naming things.

At least this work: after implementing a no-op macro, I get an error that i18n_fmt isn't a function, confirming this approach will possibly work.

Disclaimer: I still don't know what I'm doing, it's my first procedural macro.

Macro programming

A procedural macro is implemented with a function that gets a TokenStream, a list of token, and returns a TokenStream. The identity function would return what it gets. You can inspect, modify, do whatever you want with the token stream. A panic will cause a compilation error in the client code (code using the macro): this is how we'll report an error.

The no-op macro:

#[proc_macro]
pub fn i18n_fmt(body: TokenStream) -> TokenStream {
    body
}

This is the signature for the procedural macro entry point. This is the function called by the compiler when it encounters the macro in the source. The name of the function is the name of the macro.

The test has to be in a separate test as you can't invoke a procedural macro from its own crate. test/macro.rs contains:

use i18n_experiment::*;

#[test]
fn i18n_fmt_works() {
    let s = i18n_fmt! {
        i18n_fmt("This string {}", "formatted")
    };
    assert_eq!(&s, "This string formatted");
}

Runing cargo test give the first error:

error[E0423]: expected function, found macro `i18n_fmt`
  --> tests/macro.rs:11:13
   |
11 |         s = i18n_fmt("This string {}", "formatted");
   |             ^^^^^^^^ not a function
   |
help: use `!` to invoke the macro
   |
11 |         s = i18n_fmt!("This string {}", "formatted");
   |                     +

It doesn't complain about the syntax, but the macro does nothing, and i18n_fmt is not a function. Expected.

We need to replace i18n_fmt with a macro. It's just i18n_fmt! but we are working with tokens, not with text. i18n_fmt is an identifier, which is important because we don't want to replace something in a string.

Let's replace it.

use proc_macro::{Ident, Span, TokenStream, TokenTree};

#[proc_macro]
pub fn i18n_fmt(body: TokenStream) -> TokenStream {
    body.into_iter()
        .map(|tt| {
            if let TokenTree::Ident(ref ident) = tt {
                if &ident.to_string() == "i18n_fmt" {
                    return TokenTree::Ident(Ident::new("gettext!", Span::call_site()));
                }
            }
            tt
        })
        .collect()
}

We call map() on the tokens and if we encounter the proper identifier, we replace it with the macro identifier.

This should work.

error: proc macro panicked
  --> tests/macro.rs:8:13
   |
8  |       let s = i18n_fmt! {
   |  _____________^
9  | |         i18n_fmt("This string {}", "formatted")
10 | |     };
   | |_____^
   |
   = help: message: `"gettext!"` is not a valid identifier

Nope. Apparently gettext! isn't a proper identifier. This is checked at runtime (the runtime of the proc macro) so the compilation failed. Looks like we can't insert a macro with Ident::new.

Quote me

Digging a little bit in some documentation I learn about the quote!() macro. That right a macro to write a macro. Whatever you pass to the macro is turned into a TokenStream. So quote!(gettext!) should be enough.

quote! is provided by the crate of the same name, maintained by David Tolnay. The only caveat is that it returns a proc_macro2::TokenStream instead of a proc_macro::TokenStream. But just call .into() as needed.

proc_macro2 is just proc_macro but as an external crate that can be used in other contexts. It's not really essential to understand here, we are just putting some pieces together.

Add quote to your Cargo.toml. I'll spare the iteration, you need to emit a TokenTree::Group with the result of quote!.

use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use quote::quote;

#[proc_macro]
pub fn i18n_fmt(body: TokenStream) -> TokenStream {
    body.into_iter()
        .map(|tt| {
            if let TokenTree::Ident(ref ident) = tt {
                if &ident.to_string() == "i18n_fmt" {
                    return TokenTree::Group(Group::new(Delimiter::None, quote!(gettext!).into()));
                }
            }
            tt
        })
        .collect()
}

Compile this and get:

error: cannot find macro `gettext` in this scope
  --> tests/macro.rs:8:13
   |
8  |       let s = i18n_fmt! {
   |  _____________^
9  | |         i18n_fmt("This string {}", "formatted")
10 | |     };
   | |_____^
   |
   = note: this error originates in the macro `i18n_fmt` (in Nightly builds, run with -Z macro-backtrace for more info)

The macro didn't panic, but the compilation failed in the macro expansion. Looks like we are missing gettext. Add it to the cargo, and in the test file add use gettextrs::*;. It should build. But ideally we don't want to have to use gettext in the client code to use the macro. This allowed us to test that it's working the way we want. We use the gettext! to use a localized string as format, and I can tell you xgettext will find it. xgettext --keyword=i18n_fmt tests/macro.rs -o - will show you that.

How can we ensure that gettext is used when calling the macro?

Into good use

We should emit the use in the macro expansion. I have no idea how to produce the tokens, but we already use quote!, so why shall we make our life hard? quote!(use gettextrs::*;) should do it. The TokenStream passed to the procedural macro is the content of that block. We need to prepend this statement, we'll use .extend() to extend this new TokenStream with what we did before. And because we need a block, we need to put that TokenStream into a TokenTree::Group delimited with Brace.

use proc_macro::{Delimiter, Group, TokenStream, TokenTree};
use quote::quote;

#[proc_macro]
pub fn i18n_fmt(body: TokenStream) -> TokenStream {
    let mut macro_block: TokenStream = quote!(
        use gettextrs::*;
    )
    .into();
    macro_block.extend(
        body.into_iter()
            .map(|tt| {
                if let TokenTree::Ident(ref ident) = tt {
                    if &ident.to_string() == "i18n_fmt" {
                        return TokenTree::Group(Group::new(Delimiter::None, quote!(gettext!).into()));
                    }
                }
                tt
            })
    );

    [TokenTree::Group(Group::new(Delimiter::Brace, macro_block))]
        .into_iter()
        .collect()
}

So if you call it like this:

    let s = i18n_fmt! {
        i18n_fmt("This string {}", "formatted")
    };

The macro expansion will end up being something like:

    let s = {
        use gettextrs::*;
        gettext!("This string {}", "formatted")
    };

Technically the gettext! macro is also expanded, which cargo expand will, but the snippet above should compile.

That's basically it. The resulting crate is a bit longer because I also handle ngettext! that is used for plural forms.

Bottom line

While writing a procedural macro looked like a daunting task, in the end it was not as complicated as I thaught and it offers a lot of value, worth the initial effort.

Postscriptum

And now I wonder if I should have discarded declarative macros for this. Maybe it would just have worked.

1

See postscriptum.

2

True story. In my youth I prefered Pascal, so I wrote a set of C macros so I could write almost Pascal to compile with C, like:

#define PROCEDURE void
#define BEGIN {
#define END }
#define IF if(
#define THEN )
#define RETURN return

PROCEDURE something()
BEGIN
    IF done THEN
        RETURN;
END

I stopped quickly, it was idiotic.

February 03, 2023

#81 FOSDEM Weekend

Update on what happened across the GNOME project in the week from January 27 to February 03.

Miscellaneous

Sonny announces

It’s FOSDEM time! FOSDEM is a free event for software developers to meet, share ideas and collaborate.

It’s happening this weekend in Brussels on Saturday 04/02 and Sunday 05/02.

Find us at the GNOME booth or join the GNOME social event.

GNOME Development Tools

GNOME Builder

IDE for writing GNOME-based software.

hergertme reports

Builder’s project tree has been ported away from GtkTreeView to GtkListView to improve scalability and allow for more modern features going forward. This also includes support for Drag-n-Drop using the new GTK 4 APIs.

hergertme says

Builder also has received updates to global search to support filtering and previewing search results.

GNOME Incubating Apps

Sophie announces

Loupe has been accepted into the GNOME Incubator. The GNOME incubation process is for apps that are designated to be accepted into GNOME Core or GNOME Development Tools if they reach the required maturity.

The target for Loupe is to become the new Image Viewer in GNOME Core. You can track the progress in the the Core milestone.

Loupe

A simple and modern image viewer.

Sophie says

While we are busy working on the core part of Loupe – the image-loading machinery – we still have a bunch of news:

  • Landed a change in GTK that allows us to use swipe gestures to navigate through images
  • Mostly re-implemented image browsing fixing many bugs and making navigating through images much smoother
  • Added support for moving images to the trash
  • Added tracking of changes in image directory and reloading images when changed
  • Added a bunch of missing shortcuts
  • Fixed many small bugs

GNOME Circle Apps and Libraries

Warp

Fast and secure file transfer.

Fina announces

Warp 0.4 has been released with QR Code support. Scan the code with any of the supported apps and begin the transfer immediately.

Other new features: Select custom save location and seasonal progress bar colors.

We also have experimental Windows support now, see README.md for more details.

Gaphor

A simple UML and SysML modeling tool.

Arjan announces

The next release of Gaphor will have full dark-mode integration, even in the diagrams. The CSS features for diagrams have been extended to allow for special styling for dark and light mode.

Third Party Projects

Fyodor Sobolev announces

Cavalier is an audio visualizer based on CAVA. Now it has 4 drawing modes, several new options to customize interface, keyboard shortcuts for changing most of the settings on the fly, and an ability to import/export settings.

lwildberg says

This week the app “Schemes”, an app to create and edit syntax highlighting style-schemes for GtkSourceView written by Christian Hergert, was submitted to flathub and is now available there. Have fun while being creative and produce beautiful new styles!

Iman Salmani announces

I’m working on a iplan application for planning personal life and workflow. its not ready for publish but i cant stop myself.(mybe its ready for next week) i have warm welcome to contribution and suggestions. in version 1 i will focus on offline features and availability.

Fyodor Sobolev reports

Hello TWIG! My first post here, I should have started writing here earlier, but better late than never 😅 Time Switch is an app to run tasks after a timer. In the latest release there was added an ability to set clock time in 24h format when a timer should finish, and also a timer can now be paused.

Daniel Wood announces

Design, a 2D computer aided design (CAD) application for GNOME was released this week. Features:

  • Supports the industry standard DXF format
  • Uses common CAD workflows, commands and canvas management
  • Drawing creation and manipulation using command line or toolbar input
  • Layer management and manipulation
  • Entity interrogation and modification

Design 43-alpha2 is available from Flathub: https://flathub.org/apps/details/io.github.dubstar_04.design

Phosh

A pure wayland shell for mobile devices.

Guido says

This week we released phosh 0.24.0 and related components for the Linux based mobile phones near you. See here for a summary of changes.

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!

February 02, 2023

Hackweek 2023

Hack Week is the time SUSE employees experiment, innovate & learn interruption-free for a whole week! Across teams or alone, but always without limits.

This year the Hack Week was this week, the last week of January and for my first SUSE hack week I decided to work in something funny, LILS.

Linux Immersive Learning System (LILS)

I don't think that this is a good name, but don't focus on it. The main idea of this project is to create some basic machinery to be able to write "interactive" tutorials or games using the INK language.

This is not an original idea, indeed all I've done is something that's currently working on EndlessOS, and was the main idea behind the dead project Hack Computer, you can even take a look to the Hack app in flathub. But I wanted to work around this, and create something simpler, from scratch.

I wanted to build something simple, with just Python, and make it simple enough to be able to build other tools on top. The design is simple, an INK parser, with a simple game runner. In the INK script you can define commands, to do something special, and wait for events with listeners, to wait for an event in the OS to continue.

With this basic functionality it's possible to build different user interfaces for different environments. And the original idea was to make the commands and listeners something extensible with a simple API, but that's something that I have not done yet, it's all Python functions without extension point.

The code can be found in github.

The INK parser

The most complex part of this project is the INK language parser. The Ink parser is free software and there's a Linux version that you can use to parse and compile to json, but I wanted to create my own parser with Python.

I've spent most of the Hack Week time fighting with the parser and indeed was the most challenging and fun part, because I've not worked a lot with parsers and it's not something easy as pie 😛️.

I remember creating a java compiler long time ago, when I was in the Seville University, for the Language Processors course. We did that with ANTLR, so starting from that, and looking for a Python lib, I found the Lark project. So if you like regular expressions, writing a grammar is a lot more FUN.

At the end I was able to support some basic INK language with support for:

  • Text
  • Tag support
  • Options, with suppress text support
  • Knots, Stitches and Diverts
  • Include other .ink files
  • Variable definition and basic operations
  • Knots and Stitches automatic visiting count variables
  • Conditional options using variables

It still fails in some cases, the comments and TODO placed in between text is not detected correctly and there's a lot of complex stuff that's not supported yet, but with what's supported right now it's possible to create complex scripts with loops and complex game graphs, so it's good enough to build games just with it.

GNOME shell extension

To integrate with the system I've done a simple GNOME shell extension. The extension just shows the text as bubbles and options as buttons, it's really simple and I've no time to make it something ready to be used, but I was able to make something usable.

To be able to run the LILS python library from gjs I've created a simple dbus service that exposes the basic InkScript class functionality as a dbus API.

I was thinking about being able to change the desktop background, depending of the value of a background variable in the script and do something similar to play music and sounds, so it could be a cool game engine with some additions.

SUSE Hack Week

So this Hack week was really fun and I learned a lot. It's really great that SUSE does things like this, letting us work in different projects for a week, to learn, to grow or to just explore different paths.

Blocking free API access to Twitter doesn't stop abuse

In one week from now, Twitter will block free API access. This prevents anyone who has written interesting bot accounts, integrations, or tooling from accessing Twitter without paying for it. A whole number of fascinating accounts will cease functioning, people will no longer be able to use tools that interact with Twitter, and anyone using a free service to do things like find Twitter mutuals who have moved to Mastodon or to cross-post between Twitter and other services will be blocked.

There's a cynical interpretation to this, which is that despite firing 75% of the workforce Twitter is still not profitable and Elon is desperate to not have Twitter go bust and also not to have to tank even more of his Tesla stock to achieve that. But let's go with the less cynical interpretation, which is that API access to Twitter is something that enables bot accounts that make things worse for everyone. Except, well, why would a hostile bot account do that?

To interact with an API you generally need to present some sort of authentication token to the API to prove that you're allowed to access it. It's easy enough to restrict issuance of those tokens to people who pay for the service. But, uh, how do the apps work? They need to be able to communicate with the service to tell it to post tweets, retrieve them, and so on. And the simple answer to that is that they use some hardcoded authentication tokens. And while registering for an API token yourself identifies that you're not using an official client, using the tokens embedded in the clients makes it look like you are. If you want to make it look like you're a human, you're already using tokens ripped out of the official clients.

The Twitter client API keys are widely known. Anyone who's pretending to be a human is using those already and will be unaffected by the shutdown of the free API tier. Services like movetodon.org do get blocked. This isn't an anti-abuse choice. It's one that makes it harder to move to other services. It's one that blocks a bunch of the integrations and accounts that bring value to the platform. It's one that hurts people who follow the rules, without hurting the ones who don't. This isn't an anti-abuse choice, it's about trying to consolidate control of the platform.

comment count unavailable comments

Niepce January 2023 updates

This is the January 2023 update.

We start the year by a Rust implementation of the file import. It landed very early as the final stretch of work in 2022.

Then a lot of cleanups, removing no longer needed cxx bindings and no longer used C++ code. This also allow removing some "wrapper" type used in the Rust code base.

The other gphoto2-rs patch has also been merged.

Import work

Fixed some bugs ported over, like improper logic for the camera import: properly use the storage. It was just a matter of properly using the API.

Also I'm working on improving (finishing) the whole import workflow, as I want to be able to automatically sort image into folder by date, I started adding support for getting the date from files being imported. Some of the work in progress led to more patches:

All in all, fixing the stack distract from the core work, but it has to be done, and everybody wins.

I also wrote code to support automatically sorting files into folders based on the date. It will be about connecting the dots and making a UI for it.

Previews

I started looking at generalizing the thumbnailing, to become previewing. In the end it's the same thing with different processing parameters and size. And then caching all of that. Here are a few ideas:

  • back the cache with a sqlite3 database. (it needs to be persistant, but I don't think it should be part of the main database for the catalog)
  • establish a mechanism to invalidate the cache based on processing parameters.
  • make the fetch process take note of the last access.
  • finally make this totally asynchronous.

This is just conceptual as I write this. The idea is that if we ever rendered that image, we'll get it from cache the next time. Currently we do that for the thumbnails that are extracted from the EXIF, but the cache is very primitive, not cleaned up, and doesn't handle the fact that we'd want the processed image. Mind you we only have some primitive rendering of the raw image..

This leads to cleaning up the a bit the current thumbnail cache. I removed some old code that implemented the queue to just use mpsc channels from the Rust library, and added rayon to trigger the renders in parallel. It doesn't seem to make a huge difference with the thumbnailing (libopenraw extract the thumbnail from the fast and it relatively fast), but it should be better when we start rendering the whole image.

i18n

I also added my new macro crate for gettext format, i18n-format.

Build

I added the "Development" profile, which cause the development version to use a different config. The only goal here is to be able to use development snapshots independently from the development code, i.e. not risking opening a good catalog with broken development code as the database schema will evolve over time.

Cleanup

I also took a pass at removing a lot of the cxx bindings related to the import code as they are no longer necessary. Less is more.

This feels like I wrote that code for nothing, but my permanent objective on rewritting to Rust is to not lose features or introduce bugs. So the bindings were a necessary scaffolding, and I didn't really know for how long.

I also removed more old bits of C++ code that never got used, which allowed me to remove another set of bindings and will help porting more of the UI code, possibling inverting the language stack and finally having the entry point in Rust, ie. Rust will link to the C++ code instead of the other way.

Side projects

While porting libopenraw to Rust, I have implemented a small GUI app to render files for debugging. It's written with Relm, and trying to make it more usefull, I had to learn how to make component. I had to fix the documentation that was still on the older API.

This led me to think whether I could use Relm in Niepce for the UI code. Not sure how easy it is to mix, I wouldn't want to rewrite it all.

Furthermore

In the near future, I'll want to be able to automate testing the app functionality, and I think the best is to embed a scripting language. I think it will be Python. pyo3, the Rust python binding crate seems to have everything I need, the longer plan is to be able to use it for automation at a later stage. The idea came from the way FreeCAD implement their test suite.

Thank you reading.

January 31, 2023

PDF with font subsetting and a look in the future

After several days of head scratching, debugging and despair I finally got font subsetting working in PDF. The text renders correctly in Okular, goes througg Ghostscript without errors and even passes an online PDF validator I found. But not Acrobat Reader, which chokes on it completely and refuses to show anything. Sigh.

The most likely cause is that the subset font that gets generated during this operation is not 100% valid. The approach I use is almost identical to what LO does, but for some reason their PDFs work. Opening both files in FontForge seems to indicate that the .notdef glyph definition is somehow not correct, but offers no help as to why.

In any case it seems like there would be a need for a library for PDF generation. Existing libs either do not handle non-RGB color spaces or are implemented in Java, Ruby or other languages that are hard to use from languages other than themselves. Many programs, like LO and Scribus, have their own libraries for generating PDF files. It would be nice if there could be a single library for that.

Is this a reasonable idea that people would actually be interested in? I don't know, but let's try to find out. I'm going to spend the next weekend in FOSDEM. So if you are going too and are interested in PDF generation, write a comment below or send me an email, I guess? Maybe we can have a shadow meeting in the cafeteria.

January 29, 2023

GNOME 44 Wallpapers

As we gear up for the release of GNOME 44, let’s take a moment to reflect on the visual design updates.

GNOME 44 Wallpapers

We’ve made big strides in visual consistency with the growing number of apps that have been ported to gtk4 and libadwaita, embracing the modern look. Sam has also given the high-contrast style in GNOME Shell some love, keeping it in line with gtk’s updates last cycle.

The default wallpaper stays true to our brand, but the supplementary set has undergone some bolder changes. From the popular simple shape blends to a nostalgic nod to the past with keypad and pixel art designs, there’s something for everyone. The pixelized icons made their debut in the last release, but this time we focus on GNOME Circle apps, rather than the core apps.

Another exciting development is the continued use of geometry nodes in Blender. Although the tool has a steep learning curve, I’m starting to enjoy my time with it. I gave a talk on geometry nodes and its use for GNOME wallpaper design at the Fedora Creative Freedom Summit. You can watch the stream archive recording here (and part2).

Previously, Previously, Previously

January 27, 2023

Further adventures in Apple PKCS#11 land

After my previous efforts, I wrote up a PKCS#11 module of my own that had no odd restrictions about using non-RSA keys and I tested it. And things looked much better - ssh successfully obtained the key, negotiated with the server to determine that it was present in authorized_keys, and then went to actually do the key verification step. At which point things went wrong - the Sign() method in my PKCS#11 module was never called, and a strange
debug1: identity_sign: sshkey_sign: error in libcrypto
sign_and_send_pubkey: signing failed for ECDSA "testkey": error in libcrypto"

error appeared in the ssh output. Odd. libcrypto was originally part of OpenSSL, but Apple ship the LibreSSL fork. Apple don't include the LibreSSL source in their public source repo, but do include OpenSSH. I grabbed the OpenSSH source and jumped through a whole bunch of hoops to make it build (it uses the macosx.internal SDK, which isn't publicly available, so I had to cobble together a bunch of headers from various places), and also installed upstream LibreSSL with a version number matching what Apple shipped. And everything worked - I logged into the server using a hardware-backed key.

Was the difference in OpenSSH or in LibreSSL? Telling my OpenSSH to use the system libcrypto resulted in the same failure, so it seemed pretty clear this was an issue with the Apple version of the library. The way all this works is that when OpenSSH has a challenge to sign, it calls ECDSA_do_sign(). This then calls ECDSA_do_sign_ex(), which in turn follows a function pointer to the actual signature method. By default this is a software implementation that expects to have the private key available, but you can also register your own callback that will be used instead. The OpenSSH PKCS#11 code does this by calling EC_KEY_set_method(), and as a result calling ECDSA_do_sign() ends up calling back into the PKCS#11 code that then calls into the module that communicates with the hardware and everything works.

Except it doesn't under macOS. Running under a debugger and setting a breakpoint on EC_do_sign(), I saw that we went down a code path with a function called ECDSA_do_sign_new(). This doesn't appear in any of the public source code, so seems to be an Apple-specific patch. I pushed Apple's libcrypto into Ghidra and looked at ECDSA_do_sign() and found something that approximates this:
nid = EC_GROUP_get_curve_name(curve);
if (nid == NID_X9_62_prime256v1) {
  return ECDSA_do_sign_new(dgst,dgst_len,eckey);
}
return ECDSA_do_sign_ex(dgst,dgst_len,NULL,NULL,eckey);
What this means is that if you ask ECDSA_do_sign() to sign something on a Mac, and if the key in question corresponds to the NIST P256 elliptic curve type, it goes down the ECDSA_do_sign_new() path and never calls the registered callback. This is the only key type supported by the Apple Secure Enclave, so I assume it's special-cased to do something with that. Unfortunately the consequence is that it's impossible to use a PKCS#11 module that uses Secure Enclave keys with the shipped version of OpenSSH under macOS. For now I'm working around this with an SSH agent built using Go's agent module, forwarding most requests through to the default session agent but appending hardware-backed keys and implementing signing with them, which is probably what I should have done in the first place.

comment count unavailable comments

three approaches to heap sizing

How much memory should a program get? Tonight, a quick note on sizing for garbage-collected heaps. There are a few possible answers, depending on what your goals are for the system.

you: doctor science

Sometimes you build a system and you want to study it: to identify its principal components and see how they work together, or to isolate the effect of altering a single component. In that case, what you want is a fixed heap size. You run your program a few times and determine a heap size that is sufficient for your problem, and then in future run the program with that new fixed heap size. This allows you to concentrate on the other components of the system.

A good approach to choosing the fixed heap size for a program is to determine the minimum heap size a program can have by bisection, then multiplying that size by a constant factor. Garbage collection is a space/time tradeoff: the factor you choose represents a point on the space/time tradeoff curve. I would choose 1.5 in general, but this is arbitrary; I'd go more with 3 or even 5 if memory isn't scarce and I'm really optimizing for throughput.

Note that a fixed-size heap is not generally what you want. It's not good user experience for running ./foo at the command line, for example. The reason for this is that program memory use is usually a function of the program's input, and only in some cases do you know what the input might look like, and until you run the program you don't know what the exact effect of input on memory is. Still, if you have a team of operations people that knows what input patterns look like and has experience with a GC-using server-side process, fixed heap sizes could be a good solution there too.

you: average josé/fina

On the other end of the spectrum is the average user. You just want to run your program. The program should have the memory it needs! Not too much of course; that would be wasteful. Not too little either; I can tell you, my house is less than 100m², and I spend way too much time shuffling things from one surface to another. If I had more space I could avoid this wasted effort, and in a similar way, you don't want to be too stingy with a program's heap. Do the right thing!

Of course, you probably have multiple programs running on a system that are making similar heap sizing choices at the same time, and the relative needs and importances of these programs could change over time, for example as you switch tabs in a web browser, so the right thing really refers to overall system performance, whereas what you are controlling is just one process' heap size; what is the Right Thing, anyway?

My corner of the GC discourse agrees that something like the right solution was outlined by Kirisame, Shenoy, and Panchekha in a 2022 OOPSLA paper, in which the optimum heap size depends on the allocation rate and the gc cost for a process, which you measure on an ongoing basis. Interestingly, their formulation of heap size calculation can be made by each process without coordination, but results in a whole-system optimum.

There are some details but you can imagine some instinctive results: for example, when a program stops allocating because it's waiting for some external event like user input, it doesn't need so much memory, so it can start shrinking its heap. After all, it might be quite a while before the program has new input. If the program starts allocating again, perhaps because there is new input, it can grow its heap rapidly, and might then shrink again later. The mechanism by which this happens is pleasantly simple, and I salute (again!) the authors for identifying the practical benefits that an abstract model brings to the problem domain.

you: a damaged, suspicious individual

Hoo, friends-- I don't know. I've seen some things. Not to exaggerate, I like to think I'm a well-balanced sort of fellow, but there's some suspicion too, right? So when I imagine a background thread determining that my web server hasn't gotten so much action in the last 100ms and that really what it needs to be doing is shrinking its heap, kicking off additional work to mark-compact it or whatever, when the whole point of the virtual machine is to run that web server and not much else, only to have to probably give it more heap 50ms later, I-- well, again, I exaggerate. The MemBalancer paper has a heartbeat period of 1 Hz and a smoothing function for the heap size, but it just smells like danger. Do I need danger? I mean, maybe? Probably in most cases? But maybe it would be better to avoid danger if I can. Heap growth is usually both necessary and cheap when it happens, but shrinkage is never necessary and is sometimes expensive because you have to shuffle around data.

So, I think there is probably a case for a third mode: not fixed, not adaptive like the MemBalancer approach, but just growable: grow the heap when and if its size is less than a configurable multiplier (e.g. 1.5) of live data. Never shrink the heap. If you ever notice that a process is taking too much memory, manually kill it and start over, or whatever. Default to adaptive, of course, but when you start to troubleshoot a high GC overhead in a long-lived proess, perhaps switch to growable to see its effect.

unavoidable badness

There is some heuristic badness that one cannot avoid: even with the adaptive MemBalancer approach, you have to choose a point on the space/time tradeoff curve. Regardless of what you do, your system will grow a hairy nest of knobs and dials, and if your system is successful there will be a lively aftermarket industry of tuning articles: "Are you experiencing poor object transit? One knob you must know"; "Four knobs to heaven"; "It's raining knobs"; "GC engineers DO NOT want you to grab this knob!!"; etc. (I hope that my British readers are enjoying this.)

These ad-hoc heuristics are just part of the domain. What I want to say though is that having a general framework for how you approach heap sizing can limit knob profusion, and can help you organize what you have into a structure of sorts.

At least, this is what I tell myself; inshallah. Now I have told you too. Until next time, happy hacking!

#80 Different Locales

Update on what happened across the GNOME project in the week from January 20 to January 27.

Third Party Projects

Phosh

A pure wayland shell for mobile devices.

Guido says

Maybe it’s already known that Phosh’s testsuite runs the phone shell under different locales. When doing so it takes screenshots so we can make sure translations fit under the size constraints of mobile devices and designers have an easy way to validate contributor changes. This weeks news is that we doubled the amount of screenshots taken covering most of the modal dialogs now. This is how it looks in Ukranian (the untranslated strings are from the tests itself which we don’t bother translators with):

slomo announces

GStreamer 1.22 was released this Monday, including the improvements of one year of development. Some of the highlights of the release are

  • New gtk4paintablesink and gtkwaylandsink renderers
  • AV1 video codec support improvements
  • New HLS, DASH and Microsoft Smooth Streaming adaptive streaming clients
  • Qt6 support for rendering video inside a QML scene
  • Minimal builds optimised for binary size, including only the individual elements needed
  • Playbin3, Decodebin3, UriDecodebin3, Parsebin enhancements and stabilisation
  • WebRTC simulcast support and support for Google Congestion Control
  • WebRTC-based media server ingestion/egress (WHIP/WHEP) support
  • New easy to use batteries-included WebRTC sender plugin
  • Easy RTP sender timestamp reconstruction for RTP and RTSP
  • ONVIF timed metadata support
  • New fragmented MP4 muxer and non-fragmented MP4 muxer
  • New plugins for Amazon AWS storage and audio transcription services
  • New videocolorscale element that can convert and scale in one go for better performance
  • High bit-depth video improvements
  • Touchscreen event support in navigation API
  • H.264/H.265 timestamp correction elements for PTS/DTS reconstruction before muxers
  • Improved design for DMA buffer sharing and modifier handling for hardware-accelerated video decoders/encoders/filters and capturing/rendering on Linux
  • Video4Linux2 hardware accelerated decoder improvements
  • CUDA integration and plugin improvements
  • New H.264 / AVC, H.265 / HEVC and AV1 hardware-accelerated video encoders for AMD GPUs using the Advanced Media Framework (AMF) SDK
  • New “force-live” property for audiomixer, compositor, glvideomixer, d3d11compositor etc.
  • Lots of new plugins, features, performance improvements and bug fixes

See the release notes for more details https://gstreamer.freedesktop.org/releases/1.22/

Crosswords

A simple Crossword player and Editor.

jrb announces

GNOME Crosswords 0.3.7 was released. This release features a massive internal rewrite, unlocking new functionality and giving a performance boost. Among the user visible features:

  • Custom game widget for adaptive layout, supporting animations

  • New supported puzzle type: Arrowwords

  • Support puzzles with zero or one column of clues, such as alphabet crosswords

  • New options for preferences dialog:

    • Preference: Hide puzzle sets by default and let the user select the ones they want.
    • Preference: Hide puzzles after they’re solved
  • Add tagging to puzzle-sets to provide more information to users

  • Add count of unsolved puzzles

  • Fix zoom for all game UI elements

  • Support horizontal and vertical cell dividers

  • Fixes for Onscreen Keyboard support

This release is also marked for mobile compatability. Read the full release announcement for more information. It’s available for download in Fedora and flathub.

Core Apps and Libraries

GLib

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

Michael Catanzaro announces

Natanael Copa deleted GLib’s slice allocator, which will now internally use g_malloc() and g_free(). - https://gitlab.gnome.org/GNOME/glib/-/merge_requests/2935

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!

January 26, 2023

Visit us in Brno for Linux App Summit 2023!

We are excited to bring Linux App Summit 2023 to Brno, Czech Republic, from April 21st to 23rd!

This is a conference for the Desktop Linux community, GNOME, and KDE folk to discuss the future of our app ecosystem.

Brno is where me and a few other GNOMies live, and it is a tech hub in central Europe with lots of tech companies, open source communities, and universities. Brno hosted GUACEC in 2013, Akademy in 2014, and the LibreOffice Conference in 2016.

Getting here is easier by flying through the Vienna or Prague airports and taking a train to Brno. There are train and bus options from various other locations such as Berlin, Vienna, Warsaw, Bratislava.  We prepared a page with all you need to know about the trip.

The Call for Proposals is now open until February 18th. Don’t forget to submit your talk proposal on time! linuxappsummit.org/cfp

January 25, 2023

Typesetting an entire book part V: Getting it published

Writing a book is not that difficult. Sure, it is laborious, but if you merely keep typing away day after day, eventually you end up with a manuscript. Writing a book that is "good" or one that other people would want to read is a lot harder. Still, even that is easy compared to trying to get a book published. According to various unreferenced sources on the Internet, out of all manuscripts submitted only 1 in 1000 to 1 in 10 000 gets accepted for publication. Probabilitywise this is roughly as unlikely casting five dice and getting six with all of them.

Having written a manuscript I went about tying to get it published. The common approach in most countries is that first you have to pitch your manuscript to a literary agent, and if you succeed, they will then try to pitch it to publishers. In Finland the the procedure is simpler, anyone can submit their manuscripts directly to book publishing houses without a middle man. While this makes things easier, it does not help with deciding how much the manuscript should be polished before submission. The more you polish the bigger your chances of getting published, but the longer it takes and the more work you have to do if the publisher wants to make changes to the content.

Eventually I ended up with a sort-of-agile approach. I first gathered a list of all book publishers that have published scifi recently (there were not many). Then I polished the manuscript enough so it had no obvious deficiencies and sent it to the first publisher on the list. Then I did a full revision of the text and sent it to the next one and so on. Eventually I had sent it to all of them. Very soon thereafter I had received either a rejection email or nothing at all from each one.

It's not who you are, but who you know

Since the content did not succeed in selling itself, it was time to start using connections. I have known Pertti Jarla, who is one of Finland's most popular cartoonists, for several years. He runs a small scale publishing company. Its most famous book thus far has been a re-translated version of Philip K. Dick's Do Androids Dream of Electric Sheep. I reached out to him and, long story short, the book should available in Finnish bookstores in a few weeks. The front cover looks like this.

More information in Finnish can be found on the publisher's web site. As for the obvious question of what would the book's title be in English, unfortunately the answer is "it's quite complicated to translate, actually". Basically what the title says is "A giant leap for mankind" but also not, and I have not managed to come up with a description or a translation that would not be a spoiler.

So you'll just have to wait for part VI: Getting translated. Which is an order of magnitude more difficult than getting published.

A small Rust program

I wrote a small program in Rust called cba_blooper. Its purpose is to download files from this funky looper pedal called the Blooper.

It’s the first time I finished a program in Rust. I find Rust programming a nice experience, after a couple of years of intermittent struggle to adapt my existing mental programming models to Rust’s conventions.

When I finished the tool I was surprised by the output size – initially a 5.6MB binary for a tool that basically just calls into libasound to read and write MIDI. I followed the excellent min-sized-rust guide and got that down to 1.4MB by fixing some obvious mistakes such as actually stripping the binary and building in release mode. But 1.4MB still seems quite big.

Next I ran cargo bloat and found there were two big dependencies:

  • the ‘clap‘ argument parser library
  • the ‘regex’ library, pulled in by ‘env_logger

I got ‘env_logger’ to shrink by disabling its optional features in Cargo.toml:

env_logger = { version ="0.10.0", default_features = false, features = [] }

As for ‘clap’, it seems unavoidable that it adds a few hundred KB to the binary. There’s an open issue here to improve that. I haven’t found a more minimal argument parser that looks anywhere near as nice as ‘clap’, so I guess the final binary will stay at 842KB for the time being. As my bin/directories fill up with Rust programs over the next decade, this overhead will start to add up but it’s fine for now.

It’s thanks to Rust being fun that this tool exists at all. It definitely easier to make a small C program but the story around distribution and dependency management for self-contained C programs is so not-fun that I probably wouldn’t even have bothered writing the tool in the first place.

January 24, 2023

parallel ephemeron tracing

Hello all, and happy new year. Today's note continues the series on implementing ephemerons in a garbage collector.

In our last dispatch we looked at a serial algorithm to trace ephemerons. However, production garbage collectors are parallel: during collection, they trace the object graph using multiple worker threads. Our problem is to extend the ephemeron-tracing algorithm with support for multiple tracing threads, without introducing stalls or serial bottlenecks.

Recall that we ended up having to define a table of pending ephemerons:

struct gc_pending_ephemeron_table {
  struct gc_ephemeron *resolved;
  size_t nbuckets;
  struct gc_ephemeron *buckets[0];
};

This table holds pending ephemerons that have been visited by the graph tracer but whose keys haven't been found yet, as well as a singly-linked list of resolved ephemerons that are waiting to have their values traced. As a global data structure, the pending ephemeron table is a point of contention between tracing threads that we need to design around.

a confession

Allow me to confess my sins: things would be a bit simpler if I didn't allow tracing workers to race.

As background, if your GC supports marking in place instead of always evacuating, then there is a mark bit associated with each object. To reduce the overhead of contention, a common strategy is to actually use a whole byte for the mark bit, and to write to it using relaxed atomics (or even raw stores). This avoids the cost of a compare-and-swap, but at the cost that multiple marking threads might see that an object's mark was unset, go to mark the object, and think that they were the thread that marked the object. As far as the mark byte goes, that's OK because everybody is writing the same value. The object gets pushed on the to-be-traced grey object queues multiple times, but that's OK too because tracing should be idempotent.

This is a common optimization for parallel marking, and it doesn't have any significant impact on other parts of the GC--except ephemeron marking. For ephemerons, because the state transition isn't simply from unmarked to marked, we need more coordination.

high level

The parallel ephemeron marking algorithm modifies the serial algorithm in just a few ways:

  1. We have an atomically-updated state field in the ephemeron, used to know if e.g. an ephemeron is pending or resolved;

  2. We use separate fields for the pending and resolved links, to allow for concurrent readers across a state change;

  3. We introduce "traced" and "claimed" states to resolve races between parallel tracers on the same ephemeron, and track the "epoch" at which an ephemeron was last traced;

  4. We remove resolved ephemerons from the pending ephemeron hash table lazily, and use atomic swaps to pop from the resolved ephemerons list;

  5. We have to re-check key liveness after publishing an ephemeron to the pending ephemeron table.

Regarding the first point, there are four possible values for the ephemeron's state field:

enum {
  TRACED, CLAIMED, PENDING, RESOLVED
};

The state transition diagram looks like this:

  ,----->TRACED<-----.
 ,         | ^        .
,          v |         .
|        CLAIMED        |
|  ,-----/     \---.    |
|  v               v    |
PENDING--------->RESOLVED

With this information, we can start to flesh out the ephemeron object itself:

struct gc_ephemeron {
  uint8_t state;
  uint8_t is_dead;
  unsigned epoch;
  struct gc_ephemeron *pending;
  struct gc_ephemeron *resolved;
  void *key;
  void *value;
};

The state field holds one of the four state values; is_dead indicates if a live ephemeron was ever proven to have a dead key, or if the user explicitly killed the ephemeron; and epoch is the GC count at which the ephemeron was last traced. Ephemerons are born TRACED in the current GC epoch, and the collector is responsible for incrementing the current epoch before each collection.

algorithm: tracing ephemerons

When the collector first finds an ephemeron, it does a compare-and-swap (CAS) on the state from TRACED to CLAIMED. If that succeeds, we check the epoch; if it's current, we revert to the TRACED state: there's nothing to do.

(Without marking races, you wouldn't need either TRACED or CLAIMED states, or the epoch; it would be implicit in the fact that the ephemeron was being traced at all that you had a TRACED ephemeron with an old epoch.)

So now we have a CLAIMED ephemeron with an out-of-date epoch. We update the epoch and clear the pending and resolved fields, setting them to NULL. If, then, the ephemeron is_dead, we are done, and we go back to TRACED.

Otherwise we check if the key has already been traced. If so we forward it (if evacuating) and then trace the value edge as well, and transition to TRACED.

Otherwise we have a live E but we don't know about K; this ephemeron is pending. We transition E's state to PENDING and add it to the front of K's hash bucket in the pending ephemerons table, using CAS to avoid locks.

We then have to re-check if K is live, after publishing E, to account for other threads racing to mark to K while we mark E; if indeed K is live, then we transition to RESOLVED and push E on the global resolved ephemeron list, using CAS, via the resolved link.

So far, so good: either the ephemeron is fully traced, or it's pending and published, or (rarely) published-then-resolved and waiting to be traced.

algorithm: tracing objects

The annoying thing about tracing ephemerons is that it potentially impacts tracing of all objects: any object could be the key that resolves a pending ephemeron.

When we trace an object, we look it up in the pending ephemeron hash table. But, as we traverse the chains in a bucket, we also load each node's state. If we find a node that's not in the PENDING state, we atomically forward its predecessor to point to its successor. This is correct for concurrent readers because the end of the chain is always reachable: we only skip nodes that are not PENDING, nodes never become PENDING after they transition away from being PENDING, and we only add PENDING nodes to the front of the chain. We even leave the pending field in place, so that any concurrent reader of the chain can still find the tail, even when the ephemeron has gone on to be RESOLVED or even TRACED.

(I had thought I would need Tim Harris' atomic list implementation, but it turns out that since I only ever insert items at the head, having annotated links is not necessary.)

If we find a PENDING ephemeron that has K as its key, then we CAS its state from PENDING to RESOLVED. If this works, we CAS it onto the front of the resolved list. (Note that we also have to forward the key at this point, for a moving GC; this was a bug in my original implementation.)

algorithm: resolved ephemerons

Periodically a thread tracing the graph will run out of objects to trace (its mark stack is empty). That's a good time to check if there are resolved ephemerons to trace. We atomically exchange the global resolved list with NULL, and then if there were resolved ephemerons, then we trace their values and transition them to TRACED.

At the very end of the GC cycle, we sweep the pending ephemeron table, marking any ephemeron that's still there as is_dead, transitioning them back to TRACED, clearing the buckets of the pending ephemeron table as we go.

nits

So that's it. There are some drawbacks, for example that this solution takes at least three words per ephemeron. Oh well.

There is also an annoying point of serialization, which is related to the lazy ephemeron resolution optimization. Consider that checking the pending ephemeron table on every object visit is overhead; it would be nice to avoid this. So instead, we start in "lazy" mode, in which pending ephemerons are never resolved by marking; and then once the mark stack / grey object worklist fully empties, we sweep through the pending ephemeron table, checking each ephemeron's key to see if it was visited in the end, and resolving those ephemerons; we then switch to "eager" mode in which each object visit could potentially resolve ephemerons. In this way the cost of ephemeron tracing is avoided for that part of the graph that is strongly reachable. However, with parallel markers, would you switch to eager mode when any thread runs out of objects to mark, or when all threads run out of objects? You would get greatest parallelism with the former, but you run the risk of some workers prematurely running out of data, but when there is still a significant part of the strongly-reachable graph to traverse. If you wait for all threads to be done, you introduce a serialization point. There is a related question of when to pump the resolved ephemerons list. But these are engineering details.

Speaking of details, there are some gnarly pitfalls, particularly that you have to be very careful about pre-visit versus post-visit object addresses; for a semi-space collector, visiting an object will move it, so for example in the pending ephemeron table which by definition is keyed by pre-visit (fromspace) object addresses, you need to be sure to trace the ephemeron key for any transition to RESOLVED, and there are a few places this happens (the re-check after publish, sweeping the table after transitioning from lazy to eager, and when resolving eagerly).

implementation

If you've read this far, you may be interested in the implementation; it's only a few hundred lines long. It took me quite a while to whittle it down!

Ephemerons are challenging from a software engineering perspective, because they are logically a separate module, but they interact both with users of the GC and with the collector implementations. It's tricky to find the abstractions that work for all GC algorithms, whether they mark in place or move their objects, and whether they mark the heap precisely or if there are some conservative edges. But if this is the sort of thing that interests you, voilà the API for users and the API to and from collector implementations.

And, that's it! I am looking forward to climbing out of this GC hole, one blog at a time. There are just a few more features before I can seriously attack integrating this into Guile. Until the next time, happy hacking :)

January 23, 2023

Crosswords 0.3.7: Adaptive Layout, Animations, and Arrows

Howdy folks,

It’s GNOME Crosswords release time again! This is a big release with a lot of changes. Most importantly, I was able to find some time over the holidays to do some long-overdue refactoring of the core game code.

crosswords: 143 files changed, 12619 insertions(+), 5834 deletions(-)
libipuz:     41 files changed,  2345 insertions(+),  736 deletions(-)

A number of early design decisions haven’t panned out, and these decisions  held us back from doing some very basic things with the code. While not directly impactful on the user, this refactoring unlocked some long-overdue features. In addition, loading puzzles and puzzle sets are noticeably faster now.

In this post, I’m going to cover the mobile work, Arrowwords, the hackfest, and the new preferences dialog. Let’s take a look at what’s new:

Adaptive Layout and Animations

In the previous release, I tried to make Crosswords adapt to different screen sizes. It wasn’t all that usable, so I worked more on it this cycle.

First, the good news: I cleaned up a lot of the layout bugs, and (thanks to Carlos) got some form of touch screen keyboard input working. The end result is that I changed the appinfo file to indicate we supported all screen sizes and inputs, which means it should be available on all platforms.

I also had to put all the parts of the game into a custom container widget to get the spacing right. Once that was done, it was trivial to add some animations to it. It turns out AdwAnimation is really cool — and much easier to use then I had expected. I added animations to keep the puzzle centered and to align it vertically as appropriate. Here’s how resizing  now looks:

I’m pretty impressed with how easy it was to do things like this with GTK4.

Unfortunately, despite these changes, it’s not all good news. I don’t think the final results are compelling enough for mobile devices. The touchscreen input is flaky, and the onscreen keyboard isn’t great for crossword puzzles (and is so big!) I never really figured out how input was supposed to work together, and ended up just guessing until it mostly worked. I could really use better documentation on the onscreen keyboard! Or if someone explains it to me, I’m happy to write documentation for future users.

As I don’t actually have a mobile device that runs GNOME and I don’t have a touch screen, I’ve reached the limits of what I can do without additional help. I’d love feedback or advice from anyone who knows more about this space.

Arrowwords

As part of the refactoring focus, I added a bunch of new puzzle types to libipuz (more on this in a future post!) One of these puzzle types was Arrowword puzzles — a crossword style more popular in northern Europe. This had been a semi-regular request and turned out to be easier to implement than expected. We were able to get something going relatively quickly.

I wrote the cell code, Federico helped with the svg overlay arrow rendering, and Philip helped with the testing and downloader. The end result is attractive:

Arrowword Crossword

Puzzle Sets

Due to the big holiday refactor, I was able to add a few small features I’ve wanted to add for a while. First, I changed Puzzle Sets to be opt-in. We’ve ended up accruing so many of them that it makes sense for people to choose the ones they want to play.

Along the way, I added extensions  to the puzzle sets to tag them so users can see more information before trying them. Here’s the dialog to select them.

Puzzle Set selection dialog

Additionally, I added an option to let you hide puzzles that have already been solved. This was another requested feature as the list of puzzles can get quite long.

Finally we show how many puzzles remain unsolved in the main screen. This should have been a simple feature to add, but the old code base made it surprisingly hard to implement. With the rewrite, this was only a few lines of code:

New Main screeen
The new main screen

You can also see an “Add More Puzzles” button in that screenshot.

Hackfest

We held a virtual hackfest a few weekends ago! Four people showed up, and we focused on getting this release out the door. We got most of the last release blockers done during it. Davide added Italian puzzle sets and translations, and fixed the packit CI. Rosanna finished the GNOME Mini Crosswords set and started a new, alphabetically-themed one. Federico documented how to run crosswords out of podman and got the Arrowword SVG overlay themed and working. I landed the final fixes for the tagging, and landed a bunch of build fixes to the puzzle sets.

It was a lot of fun. I will try to hold another one at the end of the next release cycle.

GNOME Mini Crosswords

What’s next?

  • We’ve wanted to add autodownload-on-start for a while, and it should be possible to do that now.
  • I don’t love the current preferences dialog. It could use some design attention and tweaks.
  • And finally, I plan to switch back to work more on the Crossword Editor. It’s overdue for some love.

Until next time!

January 18, 2023

gnome-info-collect: What we learned

Last August, we ran a research exercise using a small tool called gnome-info-collect. The tool allowed GNOME users to anonymously send us non-sensitive data about how their systems were configured. The plan was to use that data to inform our design and development decisions. We got a fantastic response to our call for participation, with over 2,500 people uploading their data to the GNOME servers.

We’ve just finished the final parts of the analysis, so it’s time to share what we’ve learned. This post is on the long side, so you might want to get a brew on before you start reading!

Research limitations

The people who provided their data with gnome-info-collect were primarily recruited via GNOME’s media channels, including Discourse and Twitter. This means that the data we collected was on a fairly particular subset of GNOME’s users: namely, people who follow our social media channels, are interested enough to respond to our call for help, and are confident installing and running a command line tool.

The analysis in this post should therefore not be treated as being representative of the entire GNOME user base. This doesn’t mean that it’s invalid – just that it has limited validity to the group we collected data from.

It should also be noted that the data from gnome-info-collect is by no means perfect. We collected information on GNOME systems rather than individual users. While there were some basic measures to avoid double counting, they weren’t foolproof, and there was nothing to stop the same person from submitting multiple reports using different accounts or systems. We also have no way to know if the systems which we got data on were the main desktops used by the reporters.

Who responded?

In total, we received 2,560 responses to gnome-info-collect. Of the 2,560 responses, 43 were removed from the dataset, due to not being GNOME installations, or being a virtual machine.

Distro used

A little over half of the responses came from a Fedora installation. The other main distros were Arch and Ubuntu.

Distro Number of responses % of responses
Fedora 1376 54.69%
Arch 469 18.64%
Ubuntu 267 10.61%
Manjaro 140 5.56%
Other 78 3.10%
EndeavourOS 66 2.62%
Debian 44 1.75%
openSUSE 38 1.51%
Pop! 38 1.51%
Total 2516 100.00%

Hardware manufacturer
The data we got on the hardware manufacturer of each system was poor quality. Many systems didn’t accurately report their manufacturer, and the names were often inconsistently written. Of the manufacturers that we could identify, Lenovo was the most common, with Dell, ASUS, HP, MSI and Gigabyte making up the bulk of the other systems.

Manufacturer Responses % Valid Responses
Lenovo 516 23.54%
Dell 329 15.01%
ASUS 261 11.91%
HP 223 10.17%
MSI 213 9.72%
Gigabyte 211 9.63%
Acer 86 3.92%
Other 353 16.10%
Total valid responses 2192 100.00%

Desktop configuration

We collected data on a number of different aspects of desktop configuration. Each of these areas are relevant to ongoing areas of design and development work.

Workspaces

We collected data on both the “workspaces on primary” and “dynamic workspaces” settings. The former controls whether each workspace is only on the primary display, or whether it spans all displays, and the latter controls whether workspaces are automatically added and removed, or whether there is a fixed number of workspaces.

In both cases, the default setting was used by the vast majority of systems, though the number of systems where the default was changed was not insignificant either. It was more common for people to change workspaces on primary, as opposed to the dynamic workspaces option.

Enabled Disabled % Enabled % Disabled Total
Workspaces on primary 2078 439 82.56% 17.44% 2517
Dynamic workspaces 2262 255 89.87% 10.13% 2517

Sharing features

GNOME’s sharing settings include a variety of features, and we collected information on which ones were enabled. When looking at this, it is important to remember that an enabled feature is not necessarily actively used.

Remote login (SSH login) was enabled more than any other sharing feature, which suggests that the gnome-info-collect respondents were relatively technical users.

Activation of the other features was relatively low, with multimedia sharing being the lowest.

Sharing Feature Systems Enabled % Enabled
Remote login 527 20.95%
Remote desktop 248 9.85%
File sharing 160 6.36%
Multimedia sharing 108 4.29%

Online accounts

Around 55% of the responses had one or more online accounts set up. (Again, an enabled feature is not necessarily used.)

Number of Online Accounts Responses % Responses
0 1115 44.30%
≥1 1402 55.70%
Total responses 2517 100.00%

Google was the most common account type, followed by Nextcloud and Microsoft. Some of the account types had very little usage at all, with Foursquare, Facebook, Media Server, Flickr and Last.fm all being active on less than 1% of systems.

Account type Responses % Responses % Responses With ≥1 Accounts
Google 1056 41.95% 75.32%
Nextcloud 398 15.81% 28.39%
Microsoft 268 10.65% 19.12%
IMAP and SMTP 153 6.08% 10.91%
Fedora 114 4.53% 8.13%
Ubuntu Single Sign-On 70 2.78% 4.99%
Microsoft Exchange 55 2.19% 3.92%
Enterprise Login (Kerberos) 50 1.99% 3.57%
Last.fm 20 0.79% 1.43%
Flickr 15 0.60% 1.07%
Media Server 9 0.36% 0.64%
Facebook 4 0.16% 0.29%
Foursquare 2 0.08% 0.14%

 

Flatpak and Flathub

Flatpak and Flathub are both important to GNOME’s strategic direction, so it is useful to know the extent of their adoption. This adoption level is also relevant to the design of GNOME’s Software app.

Over 90% of systems had Flatpak installed.

Flatpak status Responses % Responses
Installed 2344 93.13%
Not installed 173 6.87%
Total 2517 100.00%

In total, 2102 systems had Flathub fully enabled, which is 84% of all reporting systems, and 97% of systems which had flatpak installed. (The Flathub filtered status refers to Fedora’s filtered version of Flathub. This contains very few apps, so it is more like having Flathub disabled than having it enabled.)

It would be interesting to analyse Flatpak and Flathub adoption across distros.

Default browser

The default browser data referred to which browser was currently set as the default. It therefore doesn’t give us direct information about how much each browser is used.

The following table gives the results for the nine most popular default browsers. This combined the different versions of each browser, such as nightlies and development versions.

Most distros use Firefox as the default browser, so it’s unsurprising that it came out top of the list. These numbers give an interesting insight into the extent to which users are switching to one of Firefox’s competitors.

Default Browser Responses % Responses
Firefox 1797 73.14%
Google Chrome 286 11.64%
Brave 117 4.76%
Web 49 1.99%
Vivaldi 47 1.91%
LibreWolf 44 1.79%
Chromium 42 1.71%
Junction 38 1.55%
Microsoft Edge 37 1.51%
Total 2457 100.00%

Shell Extensions

gnome-info-collect gathered data on which extensions were enabled on each reporting system. This potentially points to functionality that people feel is missing from GNOME Shell.

Extension usage levels

When analyzing extension usage, we removed any pre-installed extensions from the data, so that data only included extensions that had been manually installed.

The vast majority of systems – some 83% – had at least one enabled extension. Additionally, 40% had between 1 and 5 enabled extensions, meaning that the majority (around 60%) had 5 or less enabled extensions.

At the same time, a substantial minority of systems had a relatively high number of enabled extensions, with around 25% of systems having between 6 and 10.

Number of Manually Enabled Extensions Number of Responses % Responses % Responses With Enabled Extensions
0 421 16.84%
1-5 1058 42.32% 50.89%
6-10 635 25.40% 30.54%
11-18 341 13.64% 16.40%
19+ 45 1.80% 2.16%
Total 2500 100.00% 100.00%

Extension popularity

The data included 588 individual extensions that were enabled. When analysing the popularity of each extension, we grouped the extensions which had similar or identical features. So, for example, “appindicator support” includes all the various status icon extensions as well. The table below shows the 25 most common enabled extension types, after grouping them in this way. Some of the extensions are included as part of GNOME’s classic mode, and we didn’t have a way to filter out those extensions which were enabled due to the classic session.

Extension Enabled Systems % Systems
Appindicator support 1099 43.66%
Gsconnect 672 26.70%
User theme 666 26.46%
Dash to dock / panel 579 23.00%
Sound output chooser 576 22.88%
Blur my shell 530 21.06%
Clipboard manager 510 20.26%
Caffeine 445 17.68%
System monitor 346 13.75%
Just perfection desktop 318 12.63%
Drive menu 310 12.32%
Apps menu 308 12.24%
Place menus 276 10.97%
Openweather 242 9.61%
Bluetooth quick connect 239 9.50%
Night theme switcher 208 8.26%
Tiling assistant 184 7.31%
Launch new instance 180 7.15%
Rounded window corners 158 6.28%
Game mode 146 5.80%
Alphabetical app grid 146 5.80%
Burn my windows 140 5.56%
GNOME UI tune 116 4.61%
Auto move windows 99 3.93%
Desktop icons 98 3.89%
Background logo 2 0.08%

As can be seen, appindicator support was by far the most common extension type, with 44% of all reporting systems having it enabled. Gsconnect, user theme, dash to dock/panel, sound output chooser, blur my shell and clipboard managers were all enabled in over 20% of the responses.

Installed apps

Knowing which apps are installed was one of the most interesting and valuable aspects of the data. It was also one of the most challenging aspects to analyse, and required processing to remove duplicate and spurious entries from the data set. The data set is still by no means perfect, but it is good enough to draw some initial conclusions.

In general, we are interested in which apps get used, which apps people have a strong need for, plus which apps people really like. App installation does not directly indicate any of these things directly, and is a relatively poor indicator for measuring them. We therefore need to be careful when drawing conclusions from this part of the analysis.

Frequency distribution

The frequency distribution of installed apps is really interesting. The total number of installed apps was very high. Even after processing, the data contained over 11,000 unique app names. Within this very large number of installed apps, the 400 most common apps represented 87% of all that were installed. This bulk of popular apps was followed by a very long tail.

The number of apps and the length of the frequency distribution tail has undoubtedly been inflated by issues in the data, and more processing to improve the data quality would be helpful.

Popular apps

After removing the most obvious preinstalled apps from the data, the 20 most common installed apps were as follows:

 

App Installations % Systems
GIMP 1497 58.48%
VLC 1375 53.71%
Steam 1367 53.40%
htop 1184 46.25%
Dconf Editor 1108 43.28%
Extension Manager 984 38.44%
Inkscape 952 37.19%
Flatseal 942 36.80%
Discord 938 36.64%
Google Chrome 899 35.12%
Web 898 35.08%
Chromium 871 34.02%
Thunderbird 824 32.19%
GParted 795 31.05%
Wine 772 30.16%
OBS Studio 770 30.08%
Visual Studio Code 726 28.36%
Transmission 719 28.09%
Telegram 713 27.85%
Geary 672 26.25%

A longer list of the most common 110 installed apps is available separately.

Note that the removal of preinstalled apps from this lists was extremely rudimentary and the numbers in the list may represent some apps which are preinstalled by some distros.

The most common manually installed apps are a mixed bag of traditional Linux desktop apps, third-party proprietary apps, and newer GNOME apps. Examples of common apps in each of these categories include:

  • Traditional Linux desktop apps: GIMP, VLC, Inkscape, GParted, Transmission
  • Third party apps: Google Chrome, Steam, OBS Studio, NVIDIA Settings
  • Newer GNOME apps: Flatseal, To Do, Bottles, Sound Recorder, Builder

Conclusions

Overall, the data gives some strong hints about which features should be concentrated on by the GNOME project. It also provides evidence about which features shouldn’t be prioritised.

It needs to be remembered that, while we have evidence here about some of the decisions that some GNOME users are making, the data doesn’t give us much insight into why they are making the decisions that they are. For example, it would seem that people are installing the GIMP, but for what purpose? Likewise, while we know that people are enabling some features over others, the data doesn’t tell us how those features are working for them. Do people find online accounts to be useful? The data doesn’t tell us.

We therefore need to be very careful when making decisions based on the data that we have here. However, what we do have is a great basis for followup research which, when combined with these results, could be very powerful indeed.

The app installation picture is complex. On the one hand, it doesn’t look like things have changed very much in the past 10 years, with people continuing to install the GIMP, Wine, and GParted. On the other hand, we have 3rd party apps being widely used in a way that wasn’t possible in the past, and it’s exciting to see the popularity of new GNOME apps like Flatseal, To Do, Bottles, and Fragments.

The data on apps is also some of the most limited. We need data on which apps are being used, not just just which ones are installed. It would also be really helpful to have data on which apps people feel are essential for them, and it would be great to have demographic information as part of the dataset, so we can see whether there are different groups of users who are using different apps.

Methodological lessons

gnome-info-collect was the first data collection exercise of its kind that has been run by the GNOME project, and we learned a lot through the process. I’m hopeful that those lessons will be useful for subsequent research. I have notes on all that, which I’ll share at some point. For now, I just want to touch on some general points.

First, doing small standalone research exercises seems to be a great approach. It allowed us to ask research questions around our current interests, and then generate research questions for followup exercises. This allows an iterative learning process which is strongly connected to our day to day work, and which can combine different research methods to understand the data from different perspectives.

Second, the whole premise of gnome-info-collect was to do a quick and lean initiative. In reality, it turned out to be a lot more work than anticipated! This was largely due to it being the first exercise of its kind, and there not being a preexisting platform for gathering the data. However, I think that we also need to acknowledge that even lean research exercises can be a lot of work, particularly if you want to gather large amounts of data.

Finally, we discovered some major issues with the data that we can get from Linux systems. Perhaps unsurprisingly, there was a lot of inconsistency and a lack of standardisation. This required additional processing at the analysis stage, and makes automated analysis difficult. If we want to routinely collect information from GNOME systems, cleaning up the raw data would be a big help.

Outro

I’d like to take this opportunity to thank Vojtěch Staněk for all his work on gnome-info-collect. Vojtěch handled all the technical aspects of gnome-info-collect, from writing the code to processing the data, as well as helping with public outreach. He did a great job!

The gnome-info-collect data doesn’t include directly identifying information, or anything very sensitive. However, there are still privacy concerns around it. We are therefore going to be archiving the data in a restricted location, rather than publishing it in full.

However, if members of the GNOME project have a use for the data, access can be arranged, so just get in touch. We are also currently investigating options for making some of the data available in a way that mitigates any privacy risks.

January 17, 2023

libinput and the custom pointer acceleration function

After 8 months of work by Yinon Burgansky, libinput now has a new pointer acceleration profile: the "custom" profile. This profile allows users to tweak the exact response of their device based on their input speed.

A short primer: the pointer acceleration profile is a function that multiplies the incoming deltas with a given factor F, so that your input delta (x, y) becomes (Fx, Fy). How this is done is specific to the profile, libinput's existing profiles had either a flat factor or an adaptive factor that roughly resembles what Xorg used to have, see the libinput documentation for the details. The adaptive curve however has a fixed behaviour, all a user could do was scale the curve up/down, but not actually adjust the curve.

Input speed to output speed

The new custom filter allows exactly that: it allows a user to configure a completely custom ratio between input speed and output speed. That ratio will then influence the current delta. There is a whole new API to do this but simplified: the profile is defined via a series of points of (x, f(x)) that are linearly interpolated. Each point is defined as input speed in device units/ms to output speed in device units/ms. For example, to provide a flat acceleration equivalent, specify [(0.0, 0.0), (1.0, 1.0)]. With the linear interpolation this is of course a 45-degree function, and any incoming speed will result in the equivalent output speed.

Noteworthy: we are talking about the speed here, not any individual delta. This is not exactly the same as the flat acceleration profile (which merely multiplies the deltas by a constant factor) - it does take the speed of the device into account, i.e. device units moved per ms. For most use-cases this is the same but for particularly slow motion, the speed may be calculated across multiple deltas (e.g. "user moved 1 unit over 21ms"). This avoids some jumpyness at low speeds.

But because the curve is speed-based, it allows for some interesting features too: the curve [(0.0, 1.0), (1.0, 1.0)] is a horizontal function at 1.0. Which means that any input speed results in an output speed of 1 unit/ms. So regardless how fast the user moves the mouse, the output speed is always constant. I'm not immediately sure of a real-world use case for this particular case (some accessibility needs maybe) but I'm sure it's a good prank to play on someone.

Because libinput is written in C, the API is not necessarily immediately obvious but: to configure you pass an array of (what will be) y-values and set the step-size. The curve then becomes: [(0 * step-size, array[0]), (1 * step-size, array[1]), (2 * step-size, array[2]), ...]. There are some limitations on the number of points but they're high enough that they should not matter.

Note that any curve is still device-resolution dependent, so the same curve will not behave the same on two devices with different resolution (DPI). And since the curves uploaded by the user are hand-polished, the speed setting has no effect - we cannot possibly know how a custom curve is supposed to scale. The setting will simply update with the provided value and return that but the behaviour of the device won't change in response.

Motion types

Finally, there's another feature in this PR - the so-called "movement type" which must be set when defining a curve. Right now, we have two types, "fallback" and "motion". The "motion" type applies to, you guessed it, pointer motion. The only other type available is fallback which applies to everything but pointer motion. The idea here is of course that we can apply custom acceleration curves for various different device behaviours - in the future this could be scrolling, gesture motion, etc. And since those will have a different requirements, they can be configure separately.

How to use this?

As usual, the availability of this feature depends on your Wayland compositor and how this is exposed. For the Xorg + xf86-input-libinput case however, the merge request adds a few properties so that you can play with this using the xinput tool:


# Set the flat-equivalent function described above
$ xinput set-prop "devname" "libinput Accel Custom Motion Points" 0.0 1.0
# Set the step, i.e. the above points are on 0 u/ms, 1 u/ms, ...
# Can be skipped, 1.0 is the default anyway
$ xinput set-prop "devname" "libinput Accel Custom Motion Points" 1.0
# Now enable the custom profile
$ xinput set-prop "devname" "libinput Accel Profile Enabled" 0 0 1
The above sets a custom pointer accel for the "motion" type. Setting it for fallback is left as an exercise to the reader (though right now, I think the fallback curve is pretty much only used if there is no motion curve defined).

Happy playing around (and no longer filing bug reports if you don't like the default pointer acceleration ;)

Availability

This custom profile will be available in libinput 1.23 and xf86-input-libinput-1.3.0. No release dates have been set yet for either of those.

January 16, 2023

Status update, 16/01/2023

The tech world is busy building “AI apps” with wild claims of solving all problems. Meanwhile it’s still basically an unsolved problem to get images and text to line up nicely when making presentation slides.

I’m giving a couple of talks at FOSDEM in February so i’ve been preparing slides. I previously used Reveal.js, which has some nice layout options (like r-stretch and r-fit-text), but pretty basic Markdown support such that I ended up writing the slides in raw HTML.

A colleague turned me onto Remark.js, a simpler tool with better Markdown support and a CLI tool (Backslide), but its layout support is less developed than Reveal.js so I ended frustrated at the work necessary to lay things out neatly.

In the end, I’ve built my own tiny tool based around python-markdown and its attr_list extension, to compile Markdown to Reveal.js HTML in a way that attempts to not be hopelessly annoying. It’s a work in progress, but hopefully I can work towards becoming less frustrated while making slides. The code is here, take it if you like it and don’t ask me any questions 🙂

v3dv status update 2023-01

We haven’t posted updates to the work done on the V3DV driver since
we announced the driver becoming Vulkan 1.2 Conformant

But after reaching that milestone, we’ve been very busy working on more improvements, so let’s summarize the work done since then.

Moved more functionality to the GPU

Our implementation for Events and Occlusion Queries were both mostly CPU based. We have refactored both features with a new GPU-side implementation based on the use of compute shaders.

In addition to be more “the Vulkan way”, has additional benefits. For example, for the case of the events, we no longer need to stall on the CPU when we need to handle GPU-side event commnds, and allowed to re-enable sync_fd import/export.

VK_KHR_sampler_ycbcr_conversion

We have just landed a real implementation for this extension, based on the work of Ella Stanforth as part of her Igalia Coding Experience with us. This was a really complex work, as this feature added support for multi-plane formats, and needed to modify various parts of the driver. A big kudos to Ella for getting this tricky feature going. Also thanks to Jason Ekstrand, as he worked on a common Mesa framework for ycbcr support.

Support for new extensions

Since 1.2 got announced the following extension got exposed:

  • VK_EXT_texel_buffer_alignment
  • VK_KHR_maintenance4
  • VK_KHR_zero_initialize_workgroup_memory
  • VK_KHR_synchronization2
  • VK_KHR_workgroup_memory_explicit_layout
  • VK_EXT_tooling_info (0 tools exposed though)
  • VK_EXT_border_color_swizzle
  • VK_EXT_shader_module_identifier
  • VK_EXT_depth_clip_control
  • VK_EXT_attachment_feeback_loop_layout
  • VK_EXT_memory_budget
  • VK_EXT_primitive_topology_list_restart
  • VK_EXT_load_store_op_none
  • VK_EXT_image_robustness
  • VK_EXT_pipeline_robustness
  • VK_KHR_shader_integer_dot_product

Some miscellanea

In addition to those, we also worked on the following:

  • Implemented heuristic to decide to enable double-buffer mode, that could help to improve performance on some cases. It still needs to be enabled through the V3D_DEBUG environment variable.

  • Getting v3dv and v3d using the same shader optimization method, that would allow to reuse more code between the OpenGL and Vulkan driver.

  • Getting the driver working with the fossilize-db tools

  • Bugfixing, mostly related to bugs identified through new Khronos CTS releases

January 13, 2023

Looking for mentors with project proposals for Outreachy May-August 2023

Hi folks!

We are interested in sponsoring Outreachy internships projects for the May-August 2023 cohort. Project ideas should be submitted before February 7.

GNOME usually has a few participants in Outreachy, so we are looking to offer projects that are most strategic for GNOME. These include, but are not limited to, projects in the area of privacy, GTK, core experience, core applications, developer experience, and development infrastructure. More information about GNOME’s participation in Outreachy is available at Outreach/Outreachy – GNOME Wiki! .

If you would like to mentor this round, please propose a project idea at our Internship Project Ideas Gitlab project.  Once your project proposal has been reviewed, you will be asked to submit it in the Outreachy website. All project ideas need to be approved by the triage committee (Matthias Clasen, Allan Day, and Sriram Ramkrishna) and coordinators (me and Kristi) .

If you have any questions, please feel free to reply to this thread or you can message us privately at soc-admins@gnome.org.

This is a repost from https://discourse.gnome.org/t/outreachy-due-february-7-call-for-outreachy-mentors-for-may-2023-internships/13506

January 12, 2023

Some thoughts on linking to code

I have been updating the text layout roadmap for librsvg, and part of this involves describing how some of the current code works — for example, how a <text> element gets broken up into a tree of Chunk and Span structures.

Notice how those links go to the generated documentation for the library's internals. In the HTML, rustdoc adds "source" links that in turn take you to the actual source code for that item.

Aside: Chunk has documentation, but Span doesn't; I haven't gotten around to writing it yet. Linking to the documentation for the library's internals helps me see what things are undocumented yet. Maybe a struct Point { x: f64, y: f64 } doesn't need much documetation, but something more complicated like Span definitely does!

Another way of linking to these items would be to link to the actual source for Chunk and Span. Those links go to the HTML version of the source code in GitLab, for a specific commit.

For example, the link to Chunk is https://gitlab.gnome.org/GNOME/librsvg/-/blob/4cd62ad9/src/text.rs#L48-66 and it breaks down like this:

  • gitlab.gnome.org - the server, obviously
  • GNOME - group name
  • librsvg - project name
  • blob/4cd62ad9 - prefix of the git commit id
  • src/text.rs#L48-66 - the file in the librsvg source tree, plus a fragment identifier so that GitLab will highlight a range of source lines.

Obtaining that link takes a bit of work:

  • Go to the project page. Navigate down to the file you want. (Tip: hit t and start typing the filename; you'll get an autocompletion list.)

  • If you are in the wrong branch, change it at the top of the page. Note that the URL is something like .../blob/main/src/my_file.txt

  • Click on the Permalink button near the top of the page. This changes the URL to the actual latest commit id, i.e. blob/123abc/src/...

  • Go to the lines to which you want to link. Click on the first line's number, Shift-click on the second line's number. This changes the URL to a range of lines, with a fragment identifier like #L12-34.

I generally do that when writing descriptions that refer to source lines in specific revisions. Of course, if the code changes, the description may need updating if it's a "permanent" document — but at least there are hopefully never broken links, or links to source code that no longer matches the description.

The first method — linking to the documentation for the library's internals — has the advantage of being actual documentation, with a link to the source code as a bonus. I would probably prefer it if rustdoc generated links to GitLab's display of sources, instead of generating its own embedded copy of the source code, but it's not a big deal.

The second method, linking to specific revisions in GitLab's source view, is perhaps more direct when you want the reader to see the source code immediately, but it's not as easy to navigate. The reader has to read the source and there is no way in GitLab to easily jump to symbol definitions, for example, or to find the implementations for a struct's methods. The generated rustdoc has all that.

Finally, in real-time conversation, I may just link to the blob/main URLs for whatever the latest revision is at the time. I expect my interlocutor to look at the link basically immediately, or really soon after our conversation, so it will show the same context as I was thinking about.

I suppose each method has its place:

  • Link to the generated documentation for the source code — Friendly to the reader if they need more context; good for high-level exploration. Links can go out of date if you rename symbols (we don't store generated docs for each revision).

  • Link to blob/123abc or a specific revision — unambiguous, good for descriptions of code at a specific point in time, and the reader can easily cut&paste that commit id to the git command line or other tooling.

  • Link to blob/main or the latest revision in some branch — easy to do; only takes the GitLab dance to find the line numbers. Good for casual conversation. May remain current for a long time in "permanent" documentation if you are describing code very generally and that code doesn't change a lot.

January 11, 2023

Outreachy Week 5: My project and progress so far

Hi everyone!

In today’s blog post, I will explain what my internship is about and what progress I have made so far. I am an Outreachy intern with the GNOME Foundation working on the Create infrastructure for Performance tracking for librsvg project. 

librsvg is a library that is commonly used to convert SVG documents into raster images, and it is utilised by various projects such as the GNOME desktop to render their icons from SVG assets. There have been attempts to improve librsvg’s performance in terms of memory and CPU usage, but there is currently no system in place to monitor these efforts.

My internship project is to create a metrics tracking infrastructure for librsvg. This includes creating a web application to store the metrics data and render graphs for this data showing the changes in the data over time. The other part of the project is selecting the appropriate metrics and generating the metrics data reliably in CI. To do this, I intend to use cachegrind.

The work is going on quite well and I have learnt a lot over the past few weeks. So far a basic Flask backend API and a React frontend using Chart.js for graphs is ready. The code currently lives at https://gitlab.gnome.org/aryacollection.hsp/librsvg-metrics.

Thanks for reading and Happy New Year to all!

gedit on the Microsoft Store

A new version of gedit is available on the Microsoft Store: gedit for Windows.

On some search engines, when looking for "gedit", we can find suggestions or common questions such as "Is gedit available for Windows?". So I suppose there is still a demand for it.

It is also possible to install the text editor with MSYS2, but it's more complicated than just a few clicks.

Other gedit news

  • The release cadence has accelerated lately, with gedit 44 already out. gedit is now decoupled from the GNOME release schedule.
  • There is now something called Gedit Technology (inspired by the LibreOffice counterpart). It's still a work in progress.

Even though it's not possible to write comments on this blog, don't hesitate to drop me an email. I do read them, and like to have feedbacks and to exchange ideas.

What Linux distributions to recommend to computer scientists

Here is my recommendation about what kind of Linux distribution to choose for computer scientists wanting to use Linux professionally or for learning purposes, on their desktops. (Not specifically for contributing to free software).

My suggestion is to go for:

  • one of the mainstream distributions;
  • and one that has great documentation to learn a lot about system administration.

To have great and enough documentation, a distribution like Fedora or a non-LTS Ubuntu version is not what I recommend. They are released every 6 months approximately, so their system administration guide is often outdated or lacks a lot of information. These versions of distributions are more appropriate for contributors (including testers, etc).

So here is my list, in alphabetical order:

I especially recommend an enterprise distribution simply because the documentation is more complete. An "LTS" version of a distribution also has the benefit to be more stable (less bugs).

Under the documentation angle, you may give a different choice than simply comparing the graphical differences (for the installer, the default desktop and how to install packages, mainly).

Footnote: for the story, I'm now using openSUSE and I'm quite happy with the SLE / SLED documentation. In the past I've also used CentOS for certain installations, and the RHEL docs were helpful. Although I've used Debian and Ubuntu during several years each, I haven't read much of their documentation, but I suppose it's good too.

Even though it's not possible to write comments on this blog, don't hesitate to drop me an email. I do read them, and like to have feedbacks and to exchange ideas.

January 10, 2023

Post Collapse Computing Part 4: The Road Ahead

Part 1 of this series looks at the state of the climate emergency we’re in, and how we can still get our governments to do something about it. Part 2 looks at collapse scenarios we’re likely to face if we fail in those efforts, and part 3 is about concrete things we could work towards to make our software more resilient in those scenarios. In this final part we’re looking at obstacles and contradictions on the path to resilience.

Part 3 of this series was, in large parts, a pretty random list of ideas for how to make software resilient against various effects of collapse. Some of those ideas are potentially contradictory, so in this part I want to explore these contradictions, and hopefully start a discussion towards a realistic path forward in these areas.

Efficient vs. Repairable

The goals of wanting software to be frugal with resources but also easy to repair are often hard to square. Efficiency is generally achieved by using lower-level technology and having developers do more work to optimize resource use. However, for repairability you want something high-level with short feedback loops and introspection, i.e. the opposite.

An app written and distributed as a single Python file with no external dependencies is probably as good as it gets in terms of repairability, but there are serious limitations to what you can do with such an app and the stack is not known for being resource-efficient. The same applies to other types of accessible programming environments, such as scripts or spreadsheets. When it comes to data, plain text is very flexible and easy to work with (i.e. good for repairability), but it’s less efficient than binary data formats, can’t be queried as easily as a database, etc.

My feeling is that in many cases it’s a matter of choosing the right tradeoffs for a given situation, and knowing which side of the spectrum is more important. However, there are definitely examples where this is not a tradeoff, e.g. Electron is both inefficient and not very repairable due to its complexity.

What I’m more interested in is how we could bring both sides of the spectrum closer together: Can we make the repair experience for a Rust app feel more like a single-file Python script? Can we store data as plain text files, but still have the flexibility to arbitrarily query them like a database?

As with all degrowth discussions, there’s also the question whether reducing the scope of what we’re trying to achieve could make it much easier to square both goals. Similar to how we can’t keep using energy at the current rate and just swap fossil fuels out for renewables, we might have to cut some features in the interest of making things both performant and repairable. This is of course easier said than done, especially for well-established software where you can’t easily remove things, but I think it’s important to keep this perspective in mind.

File System vs. Collaboration

If you want to store data in files while also doing local-first sync and collaboration, you have a choice to make: You can either have a global sync system (per-app or system wide), or a per-file one.

Global sync: Files can use standard formats because history, permissions for collaboration, etc. are managed globally for all files. This has the advantage that files can be opened with any editor, but the downside is that copying them elsewhere means losing this metadata, so you can no longer collaborate on the file. This is basically what file sync services à la Nextcloud do (though I’m not sure to what degree these support real-time collaboration).

Per-file sync: The alternative is having a custom file format that includes all the metadata for history, sync, and collaboration in addition to the content of the file. The advantage of this model is that it’s more flexible for moving files around, backing them up, etc. because they are self-contained. The downside is that you lose access to the existing ecosystem of editors for the file type. In some cases that may be fine because it’s a novel type of content anyway, but it’s still not great because you want to ensure there are lots of apps that can read your content, across all platforms. The Fullscreen whiteboard app is an example of this model.

Of course ideally what you’d want is a combination of both: Metadata embedded in each file, but done in such a way that at least the latest version of the content can still be opened with any generic editor. No idea how feasible that’d be in general, but for text-based formats I could imagine this being a possibility, perhaps using some kind of front-matter with a bunch of binary data?

More generally, there’s a real question where this kind of real-time collaboration is needed in the first place. For which use cases is the the ability to collaborate in real time worth the added complexity (and hence reduced repairability)? Perhaps in many cases simple file sync is enough? Maybe the cases where collaboration is needed are rare enough that it doesn’t make sense to invest in the tech to begin with?

Bandwidth vs. Storage

In thinking about building software for a world with limited connectivity, it’s generally good to cache as much as possible on disk, and hitting the network as little as possible. But of course that also means using more disk space, which can itself become a resource problem, especially in the case of older computers or mobile devices. This would be accelerated if you had local-first versions of all kinds of data-heavy apps that currently only work with a network connection (e.g. having your entire photo and music libraries stored locally on disk).

One potential approach could be to also design for situations with limited storage. For example, could we prioritize different kinds of offline content in case something has to be deleted/offloaded? Could we offload large, but rarely used content or apps to external drives?

For example, I could imagine moving extra Flatpak SDKs you only need for development to a separate drive, which you only plug in when coding. Gaming could be another example: Your games would be grayed-out in the app grid unless you plug in the hard drive they’re on.

Having properly designed and supported workflows and failure states for low-storage cases like these could go a long way here.

Why GNOME?

Perhaps you’re wondering why I’m writing about this topic in the context of free software, and GNOME in particular. Beyond the personal need to contextualize my own work in the reality of the climate crisis, I think there are two important reasons: First, there’s the fact that free software may have an important role to play in keeping computers useful in coming crisis scenarios, so we should make sure it’s good at filling that role. GNOME’s position in the GNU/Linux space and our close relationships and personnel overlap with projects up and down the stack make it a good forum to discuss these questions and experiment with solutions.

But secondly, and perhaps more importantly, I think this community has the right kinds of people for the problems at hand. There aren’t very many places where low-level engineering and principled UX design are done together at this scale, in the commons.

Some resilience-focused projects are built on the very un-resilient web stack because that’s what the authors know. Others have a tiny community of volunteer developers, making it difficult to build something that has impact beyond isolated experiments. Conversely, GNOME has a large community of people with expertise all across the stack, making it an interesting place to potentially put some of these ideas into practice.

People to Learn From?

While it’s still quite rare in tech circles overall, there are some other people thinking about computing from a climate collapse point of view, and/or working on adjacent problems. While most of this work is not directly relevant to GNOME in terms of technology, I find some of the ideas and perspectives very valuable, and maybe you do as well. I definitely recommend following some of these people and projects on Mastodon :)

Permacomputing is a philosophy trying to apply permaculture-like principles to computing. The term was coined by Ville-Matias “Viznut” Heikkilä in a 2020 essay. Permaculture aims to establish natural systems that can work sustainably in the long term, and the goal with permacomputing is to do something similar for computing, by rethinking its relationship to resource and energy use, and the kinds of things we use it for. As further reading, I recommend this interview with Heikkilä and Marloes de Valk.

100 Rabbits is a two-person art collective living on a sailboat and experimenting with ideas around resilience, wherein their boat studio setup is a kind of test case for the kinds of resource constraints collapse might bring. One of their projects is uxn, a tiny, portable emulator, which serves as a super-constrained platform to build apps and games for in a custom Assembly language. I think their projects are especially interesting because they show that you don’t need fancy hardware to build fun, attractive things – what’s far more important is the creativity of the people doing it.

Screenshot of a few uxn apps and games (source)

Collapse OS is an operating system written in Forth by Virgil Dupras for a further-away future, where industrial society has not only collapsed, but when people stop having access to any working modern computers. For that kind of scenario it aims to provide a simple OS that can run on micro-controllers that are easy to find in all kinds of electronics, in order to build or repair custom electronics and simple computers.

Low Tech is an approach to technology that tries to keep things simple and resilient, often by re-discovering older technologies and recombining them in new ways. An interesting example of this philosophy in both form and content is Low Tech Magazine (founded in 2007 by Kris De Decker). Their website uses a dithered aesthetic for images that allows them to be just a few Kilobytes each, and their server is solar-powered, so it can go down when there’s not enough sunlight.

Screenshot of the Low Tech Magazine website with its battery meter background

Ink & Switch is a research lab exploring ambitious high-level ideas in computing, some of which are very relevant to resilience and autonomy, such as local-first software, p2p collaboration, and new approaches to digital identity.

p2panda is a protocol for building local-first apps. It aims to make it easy enough to build p2p applications that developers can spend their time thinking about interesting user experiences rather than focus on the basics of making p2p work. It comes with reference implementations in Rust and Typescript.

Earthstar is a local-first sync system developed by Sam Gwilym with the specific goal to be “like a bicycle“, i.e. simple, reliable, and easy enough to understand top-to-bottom to be repairable.

Funding Sources

Unfortunately, as with all the most important work, it’s hard to get funding for projects in this area. It’ll take tons of work by very skilled people to make serious progress on things like power profiling, local-first sync, or mainlining Android phones. And of course, the direction is one where we’re not only not enabling new opportunities for commerce, but rather eliminating them. The goal is to replace subscription cloud services with (free) local-first ones, and make software so efficient that there’s no need to buy new hardware. Not an easy sell to investors :)

However, while it’s difficult to find funding for this work it’s not impossible either. There are a number of public grant programs that fund projects like these regularly, and where resilience projects around GNOME would fit in well.

If you’re based in the the European Union, there are a number of EU funds under the umbrella of the Next Generation Internet initiative. Many of them are managed by dutch nonprofit NLNet, and have funded a number of different projects with a focus on peer-to-peer technology, and other relevant topics. NLNet has also funded other GNOME-adjacent projects in the past, most recently Julian’s work on the Fractal Matrix client.

If you’re based in Germany, the German Ministry of Education’s Prototype Fund is another great option. They provide 6 month grants to individuals or small teams working on free software in a variety of areas including privacy, social impact, peer-to-peer, and others. They’ve also funded GNOME projects before, most recently the GNOME Shell mobile port.

The Sovereign Tech Fund is a new grant program by the German Ministry of Economic Affairs, which will fund work on software infrasctucture starting in 2023. The focus on lower-level infrastructure means that user-facing projects would probably not be a good fit, but I could imagine, for example, low-level work for local-first technology being relevant.

These are some grant programs I’m personally familiar with, but there are definitely others (don’t hesitate to reach out if you know some, I’d be happy to add them here). If you need help with grant applications for projects making GNOME more resilient don’t hesitate to reach out, I’d be happy to help :)

What’s Next?

One of my hopes with this series was to open a space for a community-wide discussion on topics like degrowth and resilience, as applied to our development practice. While this has happened to some degree, especially at in-person gatherings, it hasn’t been reflected in our online discourse and actual day-to-day work as much as I’d hoped. Finding better ways to do that is definitely something I want to explore in 2023.

On the more practical side, we’ve had sporadic discussions about various resilience-related initiatives, but nothing too concrete yet. As a next step I’ve opened a Gitlab issue for discussion around practical ideas and initiatives. To accelerate and focus this work I’d like to do a hackfest with this specific focus sometime soon, so stay tuned! If you’d be interested in attending, let me know :)

Closing Thoughts

It feels surreal to be writing this. There’s something profoundly weird about discussing climate collapse… on my GNOME development blog. Believe me, I’d much rather be writing about fancy animations and porting apps to phones. But such are the times. The climate crisis affects, or will affect, every aspect of our lives. It’d be more surreal not to think about how it will affect my work, to ignore or compartmentalize it as a separate thing.

As I write this in late 2022, we’ve just had one of the the hottest years on record, with an unprecedented number of catastrophes across the globe. At the same time, we’ve also seen the complete inability of the current political and economic system to enact meaningful policies to actually reduce emissions. This is especially dire in the context of the new IPCC report released earlier in the year, which says that global emissions need to peak before 2025 at the latest. But instead of getting starting on the massive transition this will require, governments are building new fossil infrastructure with public money, further fueling the crisis.

Yours truly at a street blockade with Letzte Generation

But no matter how bad things get, there’s always hope in action. Whether you glue yourself to the road to force the government to enact emergency measures, directly stop emissions by blocking the expansion of coal mines, seize the discourse with symbolic actions in public places, or disincentivize luxury emissions by deflating SUV tires, there’s a wing of this movement for everyone. It’s not too late to avoid the worst outcomes – If you, too, come and join the fight.

See you in action o/

January 09, 2023

Reflections on GNOME .Asia Summit 2022

Recently, I got an opportunity to attend GNOME Asia Summit 2022 held in Kuala Lumpur. And it was an experience that I'll never forget.

It was my first time attending an in-person conference. I was not quite nervous and unsure of what to expect. However, from the moment I arrived at the conference, I was immediately struck by the welcoming and inclusive atmosphere.

GNOME swags

The major highlights of the conference were the diverse range of presentations and workshops. There were talks on a wide range of topics, from the technical aspects of GNOME development to getting started with Open Source Development. The speakers were experts in their respective fields, and their presentations were engaging and informative.

Sammy Fung Presentation

I also got an opportunity to meet and connect with other members of the GNOME community. I had the chance to chat with developers, designers, and advocates from different parts of the globe, and it was fascinating to hear about the different ways in which they are using and contributing to GNOME. Along with that, I also got to meet my fellow GSoCers/ friends at the event, and they made the event even more fun.

GNOME .Asia GSoC Group

On the final day of the conference, I got an opportunity to give a presentation on the topic "Reworking Sync Options". You can learn more about the topic here.

Aman Kumar Presentation

In addition to attending the conference, I also had the opportunity to explore the vibrant city of Kuala Lumpur. With its bustling streets, colorful markets, and delicious food, Kuala Lumpur was a feast for the senses. I especially enjoyed visiting the iconic Petronas Twin Towers and KL Tower and taking a stroll through the city. Overall, I had a wonderful time exploring Kuala Lumpur and I can't wait to visit again someday.

Petronas Towers

Kuala Lumpur city

I'd like to end the note by thanking the GNOME Organization for sponsoring my visit and allowing me to attend such a wonderful conference, and I hope to meet all these amazing people soon again.

Using the ‘glab’ CLI tool with GNOME GitLab

I like to use the glab command-line tool, which used to be a third-party project but which has apparently now been adopted by GitLab themselves. In particular, the glab mr family of commands to interact with merge requests are invaluable for checking out branches from contributors’ forks.

Since October 2022, GNOME’s GitLab instance now has a somewhat unusual configuration where the SSH hostname (ssh.gitlab.gnome.org) is different to the web/API hostname (gitlab.gnome.org). To make old checkouts continue to work, I have the following configuration in my ~/.ssh/config:

Host gitlab.gnome.org
   Hostname ssh.gitlab.gnome.org

But whether you set the SSH hostname in this way, or use the new hostname in Git remote URLs, glab will complain:

none of the git remotes configured for this repository points to a known GitLab host. Please use `glab auth login` to authenticate and configure a new host for glab

To get this to work, set the GitLab hostname to ssh.gitlab.gnome.org and the API hostname to gitlab.gnome.org. In ~/.config/glab-cli/config.yml, this looks like this:

hosts:
    ssh.gitlab.gnome.org:
        token: redacted
        api_host: gitlab.gnome.org
        git_protocol: ssh
        api_protocol: https

With this configuration, glab auth status shows incorrect API URLs, but the tool actually works:

$ glab auth status
ssh.gitlab.gnome.org
  ✓ Logged in to ssh.gitlab.gnome.org as wjt (/home/wjt/.config/glab-cli/config.yml)
  ✓ Git operations for ssh.gitlab.gnome.org configured to use ssh protocol.
  ✓ API calls for ssh.gitlab.gnome.org are made over https protocol
  ✓ REST API Endpoint: https://ssh.gitlab.gnome.org/api/v4/
  ✓ GraphQL Endpoint: https://ssh.gitlab.gnome.org/api/graphql/

I’m posting this because I spent a while trying to find a way to override the SSH hostname, before finding this issue which explains that you do it the other way around, by overriding the API hostname.

January 06, 2023

PySnooper and BitBake

Yesterday I discovered PySnooper, which describes itself as "a poor man's debugger":

Your story: You're trying to figure out why your Python code isn't doing what you think it should be doing. You'd love to use a full-fledged debugger with breakpoints and watches, but you can't be bothered to set one up right now.

I know that guy! Especially when I'm debugging some Python code in a BitBake class or recipe and attaching a debugger is even more annoying than usual. I've previously written a tiny class to start a rpdb session as needed, but I don't get on with pdb for some reason.

The example makes it look pretty awesome for quick debugging:

Source path:... example.py
Starting var:.. number = 6
11:46:07.482187 call         4 def number_to_bits(number):
11:46:07.482561 line         5     if number:
11:46:07.482655 line         6         bits = []
New var:....... bits = []
11:46:07.482732 line         7         while number:
11:46:07.482830 line         8             number, remainder = divmod(number, 2)
Modified var:.. number = 3
New var:....... remainder = 0
11:46:07.482907 line         9             bits.insert(0, remainder)
Modified var:.. bits = [0]
11:46:07.483028 line         7         while number:
11:46:07.483130 line         8             number, remainder = divmod(number, 2)
Modified var:.. number = 1
Modified var:.. remainder = 1
11:46:07.483208 line         9             bits.insert(0, remainder)
Modified var:.. bits = [1, 0]
11:46:07.483323 line         7         while number:
11:46:07.483419 line         8             number, remainder = divmod(number, 2)
Modified var:.. number = 0
11:46:07.483497 line         9             bits.insert(0, remainder)
Modified var:.. bits = [1, 1, 0]
11:46:07.483593 line         7         while number:
11:46:07.483697 line        10         return bits
11:46:07.483773 return      10         return bits
Return value:.. [1, 1, 0]
Elapsed time: 00:00:00.001749

So here's my thirty second explainer on how to use PySnooper with BitBake. First, we need to install it:

$ pip3 install pysnooper

Then you can just import pysnooper and decorate functions to get them annotated at runtime:

import pysnooper

@pysnooper.snoop()
def some_function():
    ...

That's the theory, but anyone who has tried throwing print("here") messages into classes or recipes knows this doesn't work. They execute in a child process which doesn't have standard output connected to the console, but luckily the snoop function can instead write the messages to a filename or stream or callable, which lets us glue PySnooper to BitBake's logging:

import pysnooper

@pysnooper.snoop(bb.plain)
def some_function():
    ...

As a working example, I added the annotation to get_source_date_epoch() in meta/lib/oe/reproducible.py:

import pysnooper

@pysnooper.snoop(bb.plain)
def get_source_date_epoch(d, sourcedir):
    return (
        get_source_date_epoch_from_git(d, sourcedir) or
        get_source_date_epoch_from_youngest_file(d, sourcedir) or
        fixed_source_date_epoch(d)
    )

And now when we start BitBake, we get to see the output:

Source path:... /home/ross/Yocto/poky/meta/lib/oe/reproducible.py
Starting var:.. d = <bb.data_smart.DataSmart object at 0xffff9e30dcf0>
Starting var:.. sourcedir = '/yocto/ross/build/tmp/work-shared/llvm-project-source-15.0.6-r0/git'
10:56:57.198016 call       156 def get_source_date_epoch(d, sourcedir):
10:56:57.199750 line       158         get_source_date_epoch_from_git(d, sourcedir) or
10:56:57.341387 line       157     return (
10:56:57.341978 return     157     return (
Return value:.. 1669716358
Elapsed time: 00:00:00.144763

Useful!

The default log depth is 1 so you don't see inside functions, but that can be changed when decorating You can also wrap smaller code blocks using with blocks.

The biggest catch is remembering that BitBake classes and recipes are not Python, they just have Python blocks in, so you can't decorate a function inside a class or recipe. In this case you'll need to use with block.

This looks like a very useful tool and I look forward to using it next time I'm tearing my increasingly greying hair out.

NP: Charcoal, Brambles

Account Verification: from Mastodon to CzechPoint

When Twitter’s account verification policy began to change late last year, a debate about how to do identity verification for online accounts stirred. As I found out, the way Mastodon does it is surprisingly elegant.

Previously, Twitter had a verification process for high-profile accounts (politicians, journalists, etc.). I honestly don’t know what that verification entailed, but after the Twitter takeover, Musk came up with the idea that anyone who pays $8 is eligible for verification. The ironic thing was that the new process didn’t actually include any identity verification at all. You paid $8, got a blue badge, and could impersonate anyone. This unsurprisingly didn’t work, so after a series of bummers over a short period of time, they discontinued this method of verification. They restarted it just recently and it seems to be as flawed as before.

Not that I have any major need to have my social media accounts verified, but I was wondering if there was any way to verify an account on Mastodon, because there isn’t some central entity that can verify your accounts. I found out that Mastodon goes about it in a pretty elegant way. It outsources the authentication to internet domain administrators.

The Internet domain is, in my opinion, the best “holder” of online identity. Internet domain administrators generally operate in the public interest, have long term continuity, and are globally recognized authorities. Domains are affordable and the rules for owning them are relatively loose. The chances of losing your domain, and therefore your online identity, are relatively small. Email is the most common identifier today and if you run it on your own domain, you are using the domain as an identity across online services. If you don’t have your own domain, I recommend getting one. It’s a much better idea in the long run than relying on an identity derived from accounts with service providers (Google, Facebook, Apple, Microsoft…) because with you’re just building one big vendor lock-in for yourself.

Mastodon simply uses the XHTML Friends Network format, which has been around since 2003. It allows a link to declare a relationship. So on a domain you own, you can place a link to your Mastodon profile in the format:

<a href="https://floss.social/@sesivany" rel="me">Me on Mastodon</a>

In your Mastodon profile, you link back to the page that contains the link, and when Mastodon detects the backlink, it marks the connection as verified. This will link the account to your domain. If you run, say, a popular blog on your domain and you’re generally known as the owner, that may be enough, but proving that you have control over the content of a site on a domain does not mean that you have verified your identity.

My profile with verification.

But if you own a Czech domain, you can go further. CZ.NIC allows you to link your entry in the domain registry to your account with MojeID which is also operated by CZ.NIC. This identity service is also certified to log into online government services in the EU and in order to do that requires in-person identity verification. This means that you have to go to a CzechPoint with your ID card, where someone will verify that you are really who you claim to be in MojeID (you can also use an eID card or a data box to verify your MojeID account online, but these also required in-person verification when you created them).

I have my MojeID account verified this way. So the chain of trust goes from my Mastodon account to verification with my ID at a CzechPoint. Which online service has such strong authentication? Yet from Mastodon’s side, this is a simple thing to implement and costs me about $8 in domain fees per year, not per month. And it has a much broader application. It’s a pity that XFN is not used by more services.

X servers no longer allow byte-swapped clients (by default)

In the beginning, there was the egg. Then fictional people started eating that from different ends, and the terms of "little endians" and "Big Endians" was born.

Computer architectures (mostly) come with one of either byte order: MSB first or LSB first. The two are incompatible of course, and many a bug was introduced trying to convert between the two (or, more common: failing to do so). The two byte orders were termed Big Endian and little endian, because that hilarious naming scheme at least gives us something to laugh about while contemplating throwing it all away and considering a future as, I don't know, a strawberry plant.

Back in the mullet-infested 80s when the X11 protocol was designed both little endian and big endian were common enough. And back then running the X server on a different host than the client was common too - the X terminals back then had less processing power than a smart toilet seat today so the cpu-intensive clients were running on some mainfraime. To avoid overtaxing the poor mainframe already running dozens of clients for multiple users, the job of converting between the two byte orders was punted to the X server. So to this day whenever a client connects, the first byte it sends is a literal "l" or "B" to inform the server of the client's byte order. Where the byte order doesn't match the X server's byte order, the client is a "swapped client" in X server terminology and all 16, 32, and 64-bit values must be "byte-swapped" into the server's byte order. All of those values in all requests, and then again back to the client's byte order in all outgoing replies and events. Forever, till a crash do them part.

If you get one of those wrong, the number is no longer correct. And it's properly wrong too, the difference between 0x1 and 0x01000000 is rather significant. [0] Which has the hilarious side-effect of... well, pretty much anything. But usually it ranges from crashing the server (thus taking all other clients down in commiseration) to leaking random memory locations. The list of security issues affecting the various SProcFoo implementations (X server naming scheme for Swapped Procedure for request Foo) is so long that I'm too lazy to pull out the various security advisories and link to them. Just believe me, ok? *jedi handwave*

These days, encountering a Big Endian host is increasingly niche, letting it run an X client that connects to your local little-endian X server is even more niche [1]. I think the only regular real-world use-case for this is running X clients on an s390x, connecting to your local intel-ish (and thus little endian) workstation. Not something most users do on a regular basis. So right now, the byte-swapping code is mainly a free attack surface that 99% of users never actually use for anything real. So... let's not do that?

I just merged a PR into the X server repo that prohibits byte-swapped clients by default. A Big Endian client connecting to an X server will fail the connection with an error message of "Prohibited client endianess, see the Xserver man page". [2] Thus, a whole class of future security issues avoided - yay!

For the use-cases where you do need to let Big Endian clients connect to your little endian X server, you have two options: start your X server (Xorg, Xwayland, Xnest, ...) with the +byteswappedclients commandline option. Alternatively, and this only applies for Xorg: add Option "AllowByteSwappedClients" "on" to the xorg.conf ServerFlags section. Both of these will change the default back to the original setting. Both are documented in the Xserver(1) and xorg.conf(5) man pages, respectively.

Now, there's a drawback: in the Wayland stack, the compositor is in charge of starting Xwayland which means the compositor needs to expose a way of passing +byteswappedclients to Xwayland. This is compositor-specific, bugs are filed for mutter (merged for GNOME 44), kwin and wlroots. Until those are addressed, you cannot easily change this default (short of changing /usr/bin/Xwayland into a wrapper script that passes the option through).

There's no specific plan yet which X releases this will end up in, primarily because the release cycle for X is...undefined. Probably xserver-23.0 if and when that happens. It'll probably find its way into the xwayland-23.0 release, if and when that happens. Meanwhile, distributions interested in this particular change should consider backporting it to their X server version. This has been accepted as a Fedora 38 change.

[0] Also, it doesn't help that much of the X server's protocol handling code was written with the attitude of "surely the client wouldn't lie about that length value"
[1] little-endian client to Big Endian X server is so rare that it's barely worth talking about. But suffice to say, the exact same applies, just with little and big swapped around.
[2] That message is unceremoniously dumped to stderr, but that bit is unfortunately a libxcb issue.

January 05, 2023

On the GNOME Project and “My Way or the Highway”

Disclaimers and Notes

  1. To provide examples, I will name certain entities. This is not to shame them in any way.
  2. I will be using the following terminology from Wikipedia:
    • GNOME Project: “GNOME Project is a community behind the GNOME desktop environment and the software platform upon which it is based”.
      “They” pronoun will be used.
    • GNOME: “GNOME […] is a free and open-source desktop environment”.
      “It” pronoun will be used.
  3. I am not speaking on the behalf of the GNOME Project.

Introduction

For the longest time, the GNOME Project has created many controversies because of their sheer resistance in public pressure, very opinionated and sometimes unpopular stances on the Linux desktop, and, from time to time, dismissive behavior.

While the GNOME Project is not without their faults, I often see people claim that the GNOME Project is anti-collaborative because they follow the “my way or the highway” mindset. I would like to explain where this mindset comes from, why this is an aggravated claim, and what you can do if you are negatively affected by the GNOME Project’s decisions.

What is “My Way or the Highway”?

In the context of software development, “my way or the highway” is often expressed that anyone, be it a member, contributor or user has to adhere to the philosophy, rules and standards of a project, otherwise their proposal or contribution is likely going to be rejected. In this case, “my way” means to adhere to the philosophy, rules and standards of the project, “highway” means the proposal or contribution is rejected.

Understanding the Mindset

This mindset stems from wanting to fulfill a vision or goal, while complying with a philosophy. If a person proposes an idea that is incompatible with the philosophy of a project, then that project will simply reject it.

The GNOME Project has a vision that it wants to push and perfect. Their philosophy, simply put, is “stay out of my way”. The goal is for the user to be able to do their job without being distracted, and to be encouraged to keep everything organized. To achieve this, they put everything away until the user needs something.

GNOME is designed to avoid overwhelming the user with information overload, which is a really important topic for usability for the average computer user. It is also designed to make the user’s system as clutter-free as possible. Therefore, it does not follow the traditional desktop paradigm, where there is a taskbar, desktop icons, and others - everything is put away until the user needs them. Likewise, applications also implement hamburger menus to put everything away.

This means, if one proposes the GNOME Project to implement a taskbar with an application menu, a menu bar, or any addition or change that encourages GNOME to resemble something that it isn’t supposed to be, then the GNOME Project will reject it. However, if one proposes something that improves and builds upon the GNOME Project philosophy, then it will be taken into consideration.

This type of behavior is consistent with numerous projects, including Firefox, the Linux kernel, KDE and many, many others. Even much smaller projects, like Sway, have a vision that they’d want to perfect. These projects have a scope in which they’d accept proposals and contributions. They have rules and standards that people must adhere to contribute to that project.

As an example, if I propose the Linux kernel developers to port Linux to a microkernel, then the developers will reject it. Linux’s philosophy is “[t]o not break userspace”. Porting to another kernel architecture will literally go against Linux’s philosophy: it will break many workflows in different ways, which can easily negatively affect systems at a large scale, like infrastructures, businesses, etc. Thus, the Linux kernel developers prefer to stick with the older monolithic architecture to simply avoid breaking systems and workflows.

As another example, if I propose KDE to adopt the GNOME philosophy and designs for Plasma, then KDE will reject this proposal, as it goes against their philosophy. Plasma is designed to provide a powerful desktop while following the traditional desktop paradigm, whereas GNOME is the opposite.

Even though those examples may seem over exaggerated, they go against the corresponding projects’ philosophies. Many people and companies regularly propose the GNOME Project to go against their philosophy, the same way many people and companies ask the Linux kernel developers to break userspace.

Why Does Everyone Only Talk About GNOME?

Note: this section is opinionated.

When it comes to “my way or the highway”, I noticed that users mainly reference GNOME, far more than any other project. There is one main reason I can think of.

GNOME is the most used desktop environment, therefore it is the most susceptible to gain demands and pressure from users and companies. Furthermore, it is also the most susceptible to gain media attention, like Reddit, news outlets, and many more. Even worse, this also attracts internet trolls from 4chan and Hacker News (I’m not kidding) and people who spread misinformation and disinformation.

Since a much larger portion of Linux desktop and server users use GNOME more than anything else, there are also more users who propose (and often even demand) the GNOME Project to add or make a change that goes against the philosophy. Consequently, it is a lot more common for the GNOME Project to reject those proposals. As a result, more users have a negative experience with the GNOME Project and come to the conclusion that the “my way or the highway” mindset only applies to the GNOME Project. Meanwhile, those users may likely feel comfortable with another desktop environment, so no proposals need to be made, and thus the developers of that respective project are considered as “good”, because the user has no experience with proposing something that goes against the philosophy.

People often propose the GNOME Project to be something that it is not, in which the GNOME Project has to repeatedly refuse. Since GNOME’s workflow is opinionated, many people disagree with it. The majority of people who switch to or try out Linux come from Windows, so it’s common for them to propose something that looks familiar to Windows, i.e. the traditional desktop paradigm. As mentioned before, misinformation, disinformation, and perhaps false conclusion heavily come into play too, as they often mislead people who are unaware of the GNOME Project’s philosophy and convince them based on the negativity towards the GNOME Project.

Solution

If you dislike the GNOME Project’s philosophy, then the solution is to simply not use GNOME. The Linux desktop is not Windows or macOS; it is built around options. If you dislike something, then you can use an alternative by swapping components or migrating to another Linux distribution. You, as a tinkerer, are in control over your own operating system. So, the onus is on you to install or develop anything that complies with your standards.

It is why distributions, like Arch Linux, NixOS and Gentoo exist. It is also why other desktop environments, like Plasma, still exist. Plasma is designed to be user-centric and balances with user-friendliness, whereas GNOME, on the other hand, is strictly designed to be user-friendly. It is completely normal that someone who prioritizes user-centrism would want to avoid GNOME and the GNOME Project in general. Heck, if I want to customize and tinker with my system, then I’d use Plasma or something else any day.

Conclusion

In the end, using “my way or the highway” as an argument against the GNOME Project is aggravating, as the majority of projects largely follow this mindset. These projects have a set of rules and standards that people must adhere to if they want to be a part of.

A project exists to push and perfect its vision to their target audience. It is up to you, as a user, to decide whether you agree with them or not. That being said, you have all the power to switch to another software. If GNOME isn’t for you, then you have Plasma. If Plasma and GNOME aren’t for you, then you have other desktop environments and window managers to try. Don’t forget, the Linux desktop is not Windows or macOS.


  • Edit 1: Correct typos
  • Edit 2: Add missing information
  • Edit 3: Clarify user-friendliness/centrism of DEs

January 04, 2023

2023-01-04 Wednesday

  • Back to work, mail chew - encouraged by a lovely speedup from Noel for our projection spreadsheet: seconds to tens of milliseconds to edit; nice.
  • Sales calls, sync with Gokay & Andras, worked on project description pieces. J. took down decorations.
  • Call with Ben & Kendy on our hiring pipeline - if you'd like a job around Collabora Online & LibreOffice technology you can checkout Collabora careers.
  • Bit of hacking until late, improving profiling & tracing.

January 03, 2023

2023-01-03 Tuesday

  • Some mail chew, sync with Andras, helped to seal some annoying wooden windows, tidied the house somewhat. Helped H. machine some aluminium to repair her dead Beats headphones. Up rather late working on some maths with Miriam. A more successful day off.

Crosswords: Puzzle update

Happy New Year, Crossword-lovers!

This is a minor update in preparation for a bigger release in the next few weeks. We’ve done a lot of exciting work on Crosswords over the past few months that’s almost ready for release. However, there are a few things available already that I wanted to highlight early.

Puzzles

There are two updates to the puzzle sets. First, George Ho kindly agreed to add his puzzles to the contrib puzzle-set. He’s written ten fun puzzles with the promise of more in the future! I really enjoyed solving them. There are also a few additional not-for-distribution ones available at his site.

loplop puzzle set

George has put together a truly impressive dataset of cryptic crossword clues. He’s gathered and categorized half-a-million clues over twelve years of puzzle data. I’m really impressed with this work and I’m looking to integrate some of that data into the Crossword Editor this coming year.

Second, I updated the xword-dl plugin to work with latest version of that app. Those folks have been really busy the past few months, and over ten more puzzle sets were added to it, including our first German source (Hint: a German translation would be appreciated). Our launch screen has gotten a lot fuller with both downloaders and shipped puzzles: we now have over 50 possible puzzle sets for playing across three languages.

Despite the ritual pain and suffering  with pip to get it building in a flatpak (NB: still relevant), I managed to get it updated. I’m really impressed at how that project grew in 2022. Great work!

These are available at flathub.

Developer Documentation

Federico gave me an early Christmas gift this year! He set up a developer guide for Crosswords to be built automatically from CI. It’s gorgeous, and hopefully will entice folks to join the rest of us working on the app.

A major goal I’ve had while writing this is to keep it well documented. A few weeks ago, Federico wrote beautifully about the power of documentation. As the game has grown past 30KLOC I’ve found it super valuable to reread what I wrote when revisiting older code sections. Writing the design docs has had a clarifying effect too: I had to write the WordList docs before I could finish that code, and I refresh my memory from it every time I have to touch it.

However, one thing I didn’t have was a good way to tie it all together. Now that we have this site, it’s possible to explain how things fit together. I spent some time this vacation putting together some introductory docs to make it easier to get started with the code base. If you have any interest in contributing, check them out.

What’s next?

We are gearing up for a big release in a few weeks with a lot of changes. They’re mostly internal cleanups, but these changes are already making some new features much easier to write. Here’s a preview of one them that’s in progress:

Can you guess what this will be?

The other thing I’d like to do is to hold a virtual hackfest this weekend. The exact time and date is still TBD pending some availability, but I’ve been meaning to get together with a few folks to work on some of the code together. I don’t know how well this will work virtually, but its worth a shot to open it up to a wider audience.

If you’re interested at all in word games and free software, feel free to drop by #crosswords. While there, let us know if you’re interested in the hackfest. All people are welcome. Come add a feature, add a translation, or try your hand at writing a puzzle. Or just hang out and tell us your favorite clues.

Until next time!

Download on FLATHUB

December 31, 2022

Frame pointers and other practical near-term solutions

I’m the author/maintainer of Sysprof. I took over maintainership from Søren Sandmann Pedersen years ago so we could integrate it with GNOME Builder. In the process we really expanded it’s use cases beyond just being a sampling profiler. It is a system-wide profiler that makes it very easy to see what is going on and augment that data with counters, logs, embedded files, marks, custom data sources and more.

It provides:

  • Callgraphs (based on stacktraces obtained via perf, previously via a custom kernel module).
  • A binary capture format which makes it very convenient to work with capture files to write custom tooling, unlike perf.dat or pprof.
  • Accessory collection of counters (cpu, net, mem, cpufreq, energy consumption, battery charge, etc), logs, files, and marks with visualizers for them.
  • A “control fd” that can be sendmsg()d to peers or inherited by subprocesses w/ SYSPROF_CONTROL_FD=n set allowing them to request a ring buffer (typically per-thread) to send accessory information which is muxed into the capture file.
  • Memory profilers (via LD_PRELOAD) which use the above to collect allocation records and callgraphs to visualize temporaries, leaks, etc.
  • Integration with language runtimes like GJS (GNOME’s SpiderMonkey used by GNOME shell and various applications) to use timers and SIGPROF to unwind/collect samples and mux into captures.
  • Integration with platform libraries like GLib, GTK, Mutter, and Pango which annotate the recordings with information about the GPU, input events, background operations, etc.
  • The ability to decode symbols at the tail of a recording and insert those mappings (including kallsyms) into the snapshot. This allows users to give me a .bz2 recording I can open locally without the same binary versions or Linux distribution.

This has been incredibly useful for us in GNOME because we can take someone who is having a problem, run Sysprof, and they can paste/upload the callgraph and we can address it quickly. It’s a major contributor to why we’ve been able to make GNOME so much faster in recent releases.

The Breakdown

Where this all breaks down is when we have poor/unreliable stack traces.

For example, most people want to view a callgraph upside down, starting from application’s entry points. If you can only unwind a few frames, you’re out of luck here because you can’t trace deep enough to reach the instruction pointer for main() (or similar).

You can, of course, ask perf to give you some 8 Kb of stack data on every sample. Sysprof does thousands of samples per second, so this grows quickly. Even more so the time to unwind it takes longer than reading this post. Nobody does this unless someone shows up with a
pile of money.

It’s such a problem that I made GLib/GTK avoid using libffi marshalers internally (which has exception unwind data but no frame pointers) so that it wouldn’t break Linux’s frame-pointer unwinder.

Beyond that, we started building the Flatpak org.freedesktop.Sdk with -fno-omit-frame-pointers so that we could profile software while writing it. (what a concept!) GNOME OS also is compiled with frame pointers so when really tricky things come up, many of us just use that instead of Fedora.

Yes there are cases where leaf functions are missed or not 100% correct, but it hasn’t been much of an issue compared to truncated stacks or stacks with giant holes at library boundaries because the Linux kernel frame-pointer unwinder fails.

Practical Solutions

It’s not that frame pointers are great or anything, it’s that reliability tends to be the most important characteristic. So many parts of our platform can cause profiling to give inaccurate results.

I’m not advocating for frame pointers, I’m advocating for “Works OOTB”. Fixing Python and BoringSSL to emit better instructions in the presence of frame pointers is minuscule compared to the alternatives suggested thus far.

Compiling our platforms with frame pointers is the single easiest thing we could do to ensure that we can make big wins going forward until we have a solution that reliably works across a number of failure scenarios I’ll layout below.

Profiling Needs

One necessity we have when doing desktop engineering is that some classes of problems occur in the interaction of components rather than one badly behaved component on it’s own.

Seeing profiling data across all applications, which may already be running, but also may be spawned by D-Bus or systemd during the profiling session is a must. Catching problematic processes during their startup (or failure to startup) is critical.

That means that pre-computing unwind tables is inherently unreliable for us unless we can stall any process until unwind tables are generated and uploaded for an eBPF unwinder. This may be possible, but in and of itself will skew some sorts of profiling results due to the additional latency as well as memory pressure for unwind tables (which are considerably larger than just emitting the frame pointers in the binaries .text section).

I suspect that doing something like QEMU’s live migration strategy may be an option, but again with all the caveats that it is going to perturb some sorts of results.

  1. Load eBPF program to cause all remapping of pages that are X^W to SIGSTOP. Notify an agent to setup unwind tables.
  2. Load unwind or DWARF data for all X^W pages mapped, generate system-wide tables
  3. Handle incoming requests for SIGSTOP’d processes
  4. Upload new unwind table data
  5. Repeat from #3

But, even if this were a solution today, it has a number of situations that it flat out doesn’t handle well.

Current Hurdles

  • Startup of new processes incur latency, which for some workloads relying on fork()/exec() may perturb results especially for file-based work queue processing.
  • Static binaries are a thing now. Even beyond C/C++ both Rust and golang are essentially statically linked and increasing in use across all our tooling (podman, toolbx, etc) as well as desktop applications (gtk-rs).

    This poses a huge issue. The amount of unwind data we need to load increases significantly because we can’t rely on MAP_SHARED from shared libraries to reduce the total footprint.

Again, we’re looking for whole system profiling here.

The tables become so large that they push out resident memory from the things you’re trying to profile to the point that you’re really not profiling what you think you are.

  • Containers, if allowed to version skew or if we’re unable to resolve mappings to the same inode, present a similar challenge (more below in Podman and how that is a disaster on Fedora today).

Thankfully from the Flatpak perspective, it’s very good at sharing inodes across the hard-link farm. However application binaries are increasingly Rust.

  • ORC overhead in the kernel comes in about 5Mb of extra memory at a savings of a few hundred Kb of frame pointer instructions. The value, of course, is that your instruction cache is tighter. Imagine fleets that are all static binaries and how intractable this becomes quickly. Machines at capacity will struggle to profile when you need it most.
  • Unwinding with eBPF appears to currently require exfiltrating the data via side-channels (BPF maps) rather than from the perf event stream. This can certainly be fixed with the right unwinder hooks in the kernel, but currently requires agents to not only setup unwind tables but to provide access to the unwind stacks too. My personal viewpoint is that these stacks should be part of the perf data stream, not a secondary data stream to be merged.
  • If we can’t do this thousands of times per second, it’s not fast enough.
  • If an RPM is upgraded, you lose access to the library mapped into memory from outside the process as both the inode and CRC will have changed on disk. You can’t build unwind tables, so accounting in that process breaks.

ELF/Dwarf Parsing and Privileged Processes

Parsing DWARF/.eh_frame data is very much an under researched problem by the security community. The process that sets up perf and/or BPF programs would need to do this so that unwind tables can be uploaded. You probably want the agent to be in control of that, but also very much want it sandboxed with something like bwrap (Bubblewrap) at minimum to protect the privileged agent.

Generating Missing .eh_frame Data

A fantastic entry from Oopsla 2019 talks about both validating .eh_frame data as well as synthesizing when missing by analyzing assembly instructions. This is very neat, but it also means that compiler tooling should be doing things like this to automatically generate proper .eh_frame data in the presence of inline assembly. Currently, you must get that correct by manually writing DWARF data in your assembly. Notable issues in both LLVM and glibc have created issues here.

Read more from the incredibly well written and implemented Oopsla 2019 submission [PDF].

libffi and .eh_frame

Libffi does dynamically generate enough information to unwind a stack in process across C++ exceptions. However, this is a lot more problematic if you have an agent generating unwind tables out of process. To get that data you have to map in user-space memory from the application (say /dev/$pid/mem) to access those pages and then trust that the memory isn’t malicious to the agent.

Blown FUSEs

Podman does this thing (at least for user-namespace containers on Fedora) where all the image content is served via FUSE. That means when you try to resolve the page table mappings in user-space to find the binary to locate symbols from, you are basically out of luck.

Sysprof goes through great pains by parsing layers of Podman’s JSON image information to discover what the mapping should have been. This is somewhat limited because we can only do it for the user that is running the sysprof client (as those stack frame instruction pointers are symbolized client-side in libsysprof). Doing this from an agent would require uploading that state to the agent to request integration into the unwind tables.

We have to do the same for flatpak, but thankfully /.flatpak-info contains everything we need to do that symbol resolution.

Subvolumes and OSTree deployments further complicate this matter because of indirection of mounts/mountinfo. We have to resolve through subvol=* for example to locate the correct file for the path provided in /proc/$pid/maps.

Again, since we need to build unwind tables up-front, this needs to be resolved when the profiler starts up. We can’t rely on /proc/$pid/mem because there is no guarantee the section will be mapped or that we’ll be able discover which map it was without the ELF header (which too may no longer be mapped). Since the process will likely have closed the FD after mmap(), we need to locate the proper files on disk.

Thankfully, in Sysprof we store the inode/CRC so that we can symbolize something useful if they’re incorrect, even if it’s a giant “Hey this is broken” callgraph entry.

In a world without frame-pointers, you have very little luck at making profilers reliably work in production unless you can resolve all of these issues.

There has been a lot of talk about how we can do new unwinders, and that is seriously great work! The issues above are real ones today and will become even bigger issues in the upcoming years and we’ll need all the creativity we can get to wrangle these things together.

Again, I don’t think any of us like frame pointers, just that they are generally lower effort today while also being reasonably reliable.

It might be the most practical near term solution to enable frame-pointers across Fedora today, while we push to get the rest of the system integrated to robustly support alternative unwinding capabilities.

December 29, 2022

The end of 2022

Is the end of 2022 and the beginning of 2023, so it's the right time to look back and see what great things happened during the year, and also it's the time to plan some new year resolutions.

My contribution to GNOME in 2022

I've been focused this year on the GNOME Translation Editor, migrating it to Gtk4. It's not ready yet, but I hope I will be able to have a working version soon™.

It started this summer with one intern from Outreachy, that did some initial work, building with the new Gtk4, and after the summer I started to clean the code and fixing issues, and lately, I'm trying to replace some deprecations for the next Gtk release.

so I'm working on the code, modernizing it, and replacing every usage of GtkDialog and GtkTreeView, and trying to follow the GNOME HIG as much as possible.

Live coding Streaming

I started the year spending a lot of time working on Gtk on live stream on Twitch, but after the summer I was spending less time... I didn't find the time or the energy to do it regularly.

Changes in the business

This year was also a year of changes in my work life. After the summer I left Endless, after three years of working there, to join SUSE.

I really enjoy my time at EndlessOS, the first years working on the Hack Computer and the last year working on the Endless Key. I really like the EndlessOS mission and what I was doing there, with a lot of great people.

But I was looking for something more in my career path, and I had the opportunity to join SUSE. Since September, I'm part of the Python packaging team at SUSE and I've to say that I really love this job. It's a bit different from what I have been doing before, but the maintainer life is something that I enjoy, having the opportunity to contribute to a lot of different projects and debugging and fixing random bugs is one of the most rewarding tasks to do (when you are able to find the problem).

Tumbleweed is now my favourite GNU/Linux distribution and the future of SUSE ALP looks really promising.

Changes in life

This year comes also with a lot of changes in my personal life too! At the beginning of the year I was able to bought a house, so I've now a permanent residency, after a lot of years of changing from one rent house to another one.

And this year was also the year that I was able to get the kickboxing black belt, after almost 8 years of practicing. This is the end of a learning path, and also the beginning of a different one, trying to master this fighting sport.

New year resolutions

2022 was great, but we can make 2023 even better, I'm looking always to learn and improve, so it's good to do some new year resolutions to try to complete during the next year.

  • Complete the Gtranslator migration to gtk4 and GNOME HIG improvements!
  • Be more regular with the live coding streams
  • Give some love to the PyGObject project
  • Do more serious kickboxing training and loose 10Kg
  • Play more chess games

That's all! Happy new year and Have a lot of fun...

Maps wrap-up 2022

 As I was quite busy during the days before Christmas this year I didn't get time to write the traditional holidays Maps blog post.

So I thought I should at least write a quick wrap-up of the happenings during 2022 before the end-of-year.

As always, we started out in spring with a new major release along the spring GNOME release (42.0).

In 42.0 (or rather during the development cycle leading up to it) Maps gained support for using the development profile, so that you can install nightly snapshot releases from the GNOME nightly Flatpak repo (or using locally-built bundles) in parallell with a stable release (using the Flathub release or from a „traditional“ distro package).


Like for other apps, the icon for the development release is distinguished by the „bio hazard stripe“.

Also in the 42.0 release, support for the maps: URI scheme was added. Using this a link can constructed that when opened will initiate a search using the encoded search term in Maps. This is now used by Contacts when a contact in the address book has an address set. There is also a DBus API for performing searches.

Later during spring, after the 42 release some additional spring cleaning was made.

Support for social media check-in was removed (and along with it the dependency on gnome-online-accounts). The one remaining implementation for Foursquare had not been working that reliably (and hasn't been widely used), and also the other implementation (for Facebook) had been removed one release before, and had been broken for a few years due to upstream API changes.

This was also done to get remove some mostly unused UI to get less code to port to GTK 4.

Another change was changing the process for performing OAuth setup for editing POIs in OpenStreetMap. Previously we had handled username and password locally and programmatically posted the OAuth authorization forms using a ported variant of the same code used by JOSM.

As this method is not really best-practice for doing OAuth sign-up, and this had already at one point been broken and needed fix due to changes on the server-side this was then rewritten to hand off the authorizing to use an external web browser.


   This has taken inspiration from how Cawbird implements signing in to Twitter.

Then in late June it became appearent that  due to other dependencies migrating to libsoup 3 (for HTTP request support) maintaining Maps using GTK 3, libchamplain (depending on libsoup 2.x) for GNOME 43 was looking impossible.

So, it seemed a last-minute port to use GTK 4 and our new libshumate map rendering library (based on the GTK 4 scene graph) should be attempted, even though the schedule was getting a bit tight…

But after some additional bumps (such as porting the OSM loging to use OAuth 2 due to needing to port to a new version of librest) and some frantic summer coding nights, just in time for GNOME 43 it was in working (good enough) condition.


Christopher Davis helped port the about window to use the new libadwaita version of the dialog. He also did some additional clean-ups after the GTK 4 port.

This release also involved settling on the API/ABI for libshumate and declare it as 1.0.0.

Following the release, I have also done some post-port mending to fix some brokenness left after the port.

Among the fixes, the „place bubbles“ has been fixed to get rid of the extraneous margin that unintentionally got there after the port (due to GtkPopover widgets behaving differently, they where special-cased in GTK 3 when it comes to applying the CSS spacing rules).

So after this fix the place image (when there is one) is now again „flush on“ with edge.

 This is available in the latest Maps release (43.2).

 James Westman has also been working on fixing some performance issues and memory leaks in libshumate (that has been backported to the 1.0 branch, currently the stable version released is 1.0.3).

James has also been continuing improving the vector tile support in libshumate.

I also took the time to implement one additional feature for the new major release (44.0).

Now Maps supports fetching the thumbnail and Wikipedia article extract when a place has a tag referring to an entry in Wikidata.

It is also now possible to edit those when editing a POI (and the Wikidata tag can be  „fetched“ semi-automatically when a Wikipedia article tag is filled in by pressing a reload button.


That's it for now. I said it was going to be a quick wrap-up, but I guess it got some length to it after all!

So, see ya in 2023 and happy new year!

December 28, 2022

ZCAM-js - A ZCAM implementation in JavaScript

Earlier this year I had to investigate perception of colour attributes for some of my projects. There exists a long history of colour models that try to approximate perception, each with its own shortcomings and complexities. For a decent modern colour space suitable for image processing and with…

December 27, 2022

The post-lockdown work rave, abuse, and shortened fuses

This article is a mix of personal stories, social & interpersonal psychology, and technology (with some drive-by remarks about Free & Open Source software). I originally drafted this article in the fall of 2021 and winter of 2022, but I needed more time to rewrite it a fourth time (because why not). So, while this post ends up landing straight into the twilight zone “between xmas and New Year”, it has more insights and illustrations and arguably makes for a better-rounded story, even if it’s a 7 to 9 minutes read rather than 5-6.


For all its faults and unpleasant aspects, 2020 had at least one thing going for it, in my case: the relative lack of opportunities in my line of work at the time gave me some more space to think and justification to breathe, at a time where the world was coming to terms with “It’s okay to not be 100% productive, it’s okay to try to enjoy small things in life, and just be“. I was privileged to be able to afford that suspension of affairs (due to the society I live in and the way my lifestyle is structured), and I was fortunate enough to not have had casualties in my direct entourage in light of that year’s context.

Then suddenly, in 2021, after a year of nothingness, work picked up. Hold unto your buckets!

As I often say in business, when the train passes through the station, you get onto the damned train. Or perhaps I should say, while the horse is still around while you’re being chased by a colossus, you jump onto that f’ing horse. Well, it turned out to be a surprisingly long and sporty ride.

Shadow of the Colossus pixelart” by Fernando Henrique, used with permission for editorial purposes.
Check out his amazing gallery!

The Work Rave

I initially thought 2021’s work surge would last only a short while, that I would soon have time to rest and take a step back “when the client work pipeline dries up because of some new pandemic/economic SNAFU.” The intense “hustle while you can” marathon—in reaction to the dry business year of 2020—was necessary, but it was also quite exhausting.

Continuous work with very little physical activities (lockdowns and curfews didn’t exactly improve my already sedentary lifestyle) also had a tangible impact. I don’t think I’ve ever taken as many boiling hot baths as during that year, where I had to regularly unbreak my hands—that were frequently on the verge of RSI, from too much typing and mousing—and rest my eyes (which were almost always dried out near the end of the day). My body was forcing me to slow down (later in the year for example, I had to stay away from the keyboard for two weeks or so because the hands just couldn’t type), and this was supremely annoying to me; after all, it’s not like I was sick, nor worried about food & shelter, so how could I not make the utmost productive use of my time given my arguably envious position in the context of the pandemic?

Summer went by without work drying up that year (nice problem to have, I know 🫠), so after maybe two weeks of semi-vacation (read: working on my own tasks and a GTG release, instead of client work) I went right back to the work treadmill. By that time I had experienced two micro-burnouts in the months prior, but, again, strike the iron while it’s hot, seize the work while it’s available, etc.—we didn’t know what would happen in the fall season, after all.

Witnessing abuse

Around the same time, I was also trying to support two good friends who were undergoing nervous breakdown due to criminal harassment and who, in late summer, were in deep distress. Now, if you’ve paid attention to my last few years’ retrospectives so far, you know I’ve had my share of dead friends in the past, so no, not this time, not on my watch.

I told my clients about my reduced availability during that time, and focused on helping those friends—from mere emotional and logistical support (“I have extra capacity. Use it.”, I said) to “last minute evac to the airport”, as they had to exile themselves for their own safety (because this is the harassment they’ve been going through). They have now left the country forever. It pains me that I won’t be able to see them around much anymore, but if that’s what it takes for them to stay alive, so be it.

It started out as a dark, life-threatening situation, and they are now safe and sound, albeit an ocean apart. I guess that’s a reasonable outcome.

"This is fine" dog

Facing abuse

Shortly afterwards however, an acquaintance of mine (unrelated to the situation described above) started gaslighting and bullying me. Figures! That person had previously been a good friend of mine, and they used to live by the ethos of openness and generosity towards others… until then, that is.

Indeed, within the span of a few months, money and power had irreversibly shifted that person’s worldview and abruptly brought out the worst in them, to the point where they could no longer be reasoned with—any such attempt would only bring out more furious and outlandish demands from them, and they continued to try to bully me into submission and oblivion.

I was in shock and awe at the selfish and petty power trip unfolding before my eyes.

What had happened to that person’s previous noble ideals and “standing up for your colleagues” principles? Alas, that person had now survived long enough to become the exact embodiment of the kind of patronal hubris and unfairness they had vocally despised before (when they were on the other side of the fence). My discontentment was profound:

“You were the chosen one!
It was said that you would destroy the haute bourgeoisie, not join them!
Bring fairness to the proletariat, not leave it in darkness!”

By that climatic scene, their continued bullying had brought me on the verge of 2021’s third micro-burnout, and I had now lost literally an entire week of sleep thinking about the situation and how disappointed I was in their self-centeredness… and that was enough. It had to stop.

At that point in the year—and after the “above-and-beyond” energy spent trying to salvage other difficult relationships in previous years, some with real life-threatening circumstances—I had no patience left for this kind of meaningless abuse, and sought to gracefully yet swiftly end that relationship. It is unfortunate for them as I was pretty much the only friend remaining in their life (they had previously alienated all other friends and family members… see a pattern here?), but I’m not here to save everyone against themselves, particularly not when they show aggressiveness towards me.

Sure, demonstrable facts and reality were on my side, but it wouldn’t matter: that person could no longer be reasoned with; their paranoïa made them imagine adversaries everywhere they went, and counter-PsyOps would have been way above my paygrade. I have heard enough stories of similar situations, and we ain’t got no time for that, so… Neutralize and move on. Have a nice life, buddy.

Pictured: “You want to boss me around?
Oh no, you don’t. I’m too old for this sh!t.”

Facing short fuses: a challenge of our times

You know, I understand how most of us have been in a generally irritable mood since 2020—heck, even I have much lower levels of tolerance compared to the inordinate amounts of B.S. I can normally endure, and generally speaking, I think something broke in all of us—so I try to be patient, helpful, understanding and loving to everyone I meet out there… but I have definitely seen people around me having a much shorter fuse and getting offended about everything and anything.

Even something as mundane as an email where I say, “Here’s my phone number as you requested, call me when you want, if I don’t answer I’m probably outside doing errands”. Or for cracking too many jokes in social encounters among friends. Or for pointing out that a PowerPoint or Photoshop mockup is not a MVP and that you shouldn’t erroneously use that term in front of investors if you don’t want to be instantly discredited. Or for taking more than 48 hours to reply to a friend’s email about some drying machine’s warranty enforcement options, while I receive an average of 200 to 250 emails per week. Seriously, some people around me got offended about those things! And I’m like:

At some point, my empathy has limits, and I’m not a punching bag for the perpetually-offended and self-indulgent. Yes, we’ve been living through difficult times, and yes, I do have a degree in psychology and inordinate amounts of patience, but you’re not paying me to be the übertragung medium for your personality laid bare. I’ve already had enough people attempt to play mind games on me in the last few years; no more.

In business and relationships these days, when someone is being unappreciative of my contributions, dismissive of my value, or accusing me of this or that perceived emotional slight, I am now cutting them out. Life is too short to be spent comforting self-centered or inconsiderate people.

👉 As an aside: similarly, I can empathize with various FLOSS project maintainers having very limited tolerance left for passive-aggressive commenters lately, and thus their tendency to quickly lock heated tickets in their issue trackers, as we’ve seen in some areas of GNOME in recent years. I, for one, am incredibly grateful for the many improvements we have seen throughout the GNOME applications & technology stack from 2020 to 2022 (even if I sometimes disagree with some design decisions), but details of my appreciation will probably warrant some standalone blog post or two! In the meantime: thank you, fellow contributors. Your work is not going unnoticed.


With this mindset, and after what transpired through the first three quarters of 2021, I had to take a break from any non-essential work at the start of the fall season in 2021. I had reached the limit on the amount of madness for the year, and staring at the inevitability of the dreaded year-end administrative & financial planning tasks, I decided to phase out existing client work and not take on any new clients during 2021’s last quarter.

Thus in 2022, to avoid burning myself again, I’ve then been very selective about what client work I take on (luckily, my client assignations have been very pleasant that year); I have then been trying to focus my remaining time on draining the swamp, keeping enough free time for myself and my involvement in FLOSS projects and local non-profit orgs, and preparing to scale the business in 2023-2024.

…but scaling, my friends, is a story for another day. I’ve yet to write a retrospective summary of all the cool things that happened throughout 2022, too. Assuredly, my next few blog posts will be quite a bit more upbeat! 😊

December 26, 2022

That was 2022

In non-chronological order

  • Released Pika Backup 0.4 with scheduled backups and GTK 4 & libadwaita
  • Started working on a “Welcome To GNOME” website
  • Refactored apps.gnome.org to share a lot of code with “Welcome to GNOME”
  • Reviewed some apps for GNOME Circle and made announcements for new apps that joined
  • Upped my espresso game to an entirely new level
  • Removed 2600 obsolete lines of code from GTK because YOLO
  • First time without therapy after seven years … kind of
  • Got the rona from who knows where
  • Contributed support for the keyring format to oo7
  • Got a bunch of new chisels for woodworking and linoleum print
  • Contributed to screenshot guidelines and landed new screenshots for half of the Core apps
  • Got awarded the GNOME community appreciation award
  • Met a bunch of new faces at Berlin mini GUADEC
  • Hosted a BoF about app organization
  • Brought app organization and the new incubation process to life
  • Dared to touch the Flatpak background portal to make autostarting apps work again
  • Made some minimal contributions to libadwaita for the looks
  • My money don’t jiggle jiggle
  • Managed to fix a nasty bug with notifications disappearing for Clock alarms that had been bugging me a lot
  • Finally glazed my first vase
  • Launched app.drey as a publicly available app id prefix
  • Continued the queer conspiracy
  • Dropped the overly specific Key Rack app on an unexpecting world
  • Started building a model windmill on a far too small scale
  • Started an initiative to fade out legacy app branding that had limited practical impact so far
  • Deleted all my twitter accounts
  • Been part of a successful initiative to remove gendered terms and price differences between fits from the GNOME shop
  • Tried to convince people that we want to pre-select the location shown in the file chooser with little success for the relevant people
  • Landed some features for Loupe, like rotation, zoom, scrolling, Exif metadata, and a bunch of bug fixes
  • Noticed that displaying images on computers is fundamentally impossible and flawed
  • Added support for sending links for specific views on app overview and made many new fields available
  • Finally ordered the cat ears.
Petrol espresso cup with espresso on wooden board