Welcome back to my blog, it’s been a while. Over the past few months I’ve been busier with other aspects of my life than expected, hence the lack of status updates. Despite that, I’ve made progress on various fronts.
Waylock 0.4.0 and Rewrite
Yesterday I finally released waylock 0.4.0 which has been rewritten in Zig. It now uses the ext-session-lock-v1 protocol, which I worked on earlier this year and discussed in my February 2022 update. Unfortunately, river itself does not yet support this protocol. The work is already done and there’s been a pull request implementing it open for quite some time, the only thing I’m waiting on is a wlroots release including the session lock implementation. I intend to spend some more time working on upstream wlroots to help tie up various loose ends before 0.16.0 and further develop the scene graph API.
Rewriting waylock from Rust into Zig has made me think once again about the different trade-offs made by the two languages and their ecosystems. Looking at the Rust code I wrote several years ago I am rather appalled by how many dependencies it has. If I’ve managed to use the cargo tooling correctly, I count 48 crates that waylock 0.3.5 depends on in addition to linking the system libxkbcommon and PAM libraries. That is a lot of code running inside my application that I never read let alone seriously audited.
To compare, waylock 0.4.0 depends only on libwayland, libxkbcommon, and PAM as well as thin Zig bindings I have written to make their APIs a bit more type safe.
This is also reflected in the binary size of waylock 0.3.5 vs 0.4.0. A stripped release build of the Rust version weighs in at 1.7MiB or, if the native Rust Wayland implementation is replaced by linking libwayland, 1.6MiB. A stripped “release-safe” build of the Zig version with all runtime safety checks and assertions enabled is just 69KiB.
If this makes you wonder what all that extra code in the Rust version was doing, waylock 0.3.5 did have a few more features than 0.4.0 such as reading a config file. However, even after removing the config file feature out of curiosity, the Rust version was still 1.5MiB.
The only real answer I have to this question is that the Rust version and its dependencies do a lot of abstraction, and that abstractions are seldom “zero cost” in practice despite what people may tell you.
Rewriting waylock in Zig also led me to rexamine some aspects of zig-wayland, particularly regarding its usage in client applications. The scanner (which generates zig bindings from xml protocol specifications) now requires the user to explicitly request code generation for each global Wayland interface used. For example, the application must now call e.g.
as part of its build process, passing the global interface name and the version for which to generate code. In this example the wl_surface.offset request would not be generated as it is only available since version 5.
(If you’re not familiar with Wayland, wl_surface is created through wl_compositor and inherits its version, see the Wayland docs on protocol versioning for more information.)
This solves the outstanding forward compatibility issue and allows building against newer system Wayland protocol versions than the application supports.
The knowledge of what maximum version a client application supports at compile time allows us to make another improvement as well. One of the most common Wayland client bugs I’ve seen in clients using libwayland is passing the interface version received from the server in the wl_registry.global event straight to wl_registry_bind(). A forwards compatible client must check the version advertised by the server against the maximum version the client supports, otherwise the client will be terminated when the server sends events unknown to the client.
This is a particularly nasty bug because everything will work fine until your software is around long enough for Wayland protocols to evolve. For example, Chromium recently fixed this bug. I’ve also written this bug a few times myself, including in previous Rust versions of waylock.
That’s why it’s pretty cool that zig-wayland now makes this bug impossible. Since we know the maximum version the client supports at build time, it’s trivial to insert the required version check into the generated code for zig-wayland’s wl_registry_bind() equivalent as a safeguard.
River has also gained some nice new features recently:
Automatic cursor hiding after a timeout or when typing was contributed by Duncan Overbruck. On moving the cursor after it has been hidden it will be shown again.
Initial support for the idle-inhibit protocol was contributed by dfangx. No longer shall swayidle put your monitor to sleep while watching a movie in mpv!
Mapping of lid and tablet switches was contributed by Peter Kaplan. Now you can have a command run when you close the lid of your laptop!
Peter Kaplan also contributed a very nice feature for people switching between multiple keyboard layouts: layout independent mappings. The riverctl map command has gained a -layout option taking an index into the list of layouts set with XKB_DEFAULT_LAYOUT. If passed, keyboard input will always be interpreted using the given layout, ignoring the currently active xkb layout.
Note that these features are not yet available in a released version of river, only on the git master branch. I plan to release river 0.2.0 shortly after wlroots 0.16.0 is released.
Thanks for reading and till next time!