You are here

Planet GNOME

Subscribe to Feed Planet GNOME
Planet GNOME - https://planet.gnome.org/
Përditësimi: 1 ditë 7 orë më parë

GNOME Shell and Mutter Development: What is new in GNOME Kiosk 50

Mër, 01/04/2026 - 11:06pd

GNOME Kiosk, the lightweight, specialized compositor continues to evolve In GNOME 50 by adding new configuration options and improving accessibility.

Window configuration User configuration file monitoring

The user configuration file gets reloaded when it changes on disk, so that it is not necessary to restart the session.

New placement options

New configuration options to constrain windows to monitors or regions on screen have been added:

  • lock-on-monitor: lock a window to a monitor.
  • lock-on-monitor-area: lock to an area relative to a monitor.
  • lock-on-area: lock to an absolute area.

These options are intended to replicate the legacy „Zaphod“ mode from X11, where windows could be tied to a specific monitor. It even goes further than that, as it allows to lock windows on a specific area on screen.

The window/monitor association also remains true when a monitor is disconnected. Take for example a setup where each monitor, on a multiple monitors configuration, shows different timetables. If one of the monitors is disconnected (for whatever reason), the timetable showing on that monitor should not be moved to another remaining monitor. The lock-on-monitor option prevents that.

Initial map behavior was tightened

Clients can resize or change their state  before the window is mapped, so size, position, and fullscreen as set from the configuration could be skipped. Kiosk now makes sure to apply configured size, position, and fullscreen on first map when the initial configuration was not applied reliably.

Auto-fullscreen heuristics were adjusted
  • Only normal windows are considered when checking whether another window already covers the monitor (avoids false positives from e.g. xwaylandvideobridge).
  • The current window is excluded when scanning “other” fullscreen sized windows (fixes Firefox restoring monitor-sized geometry).
  • Maximized or fullscreen windows are no longer treated as non-resizable so toggling fullscreen still works when the client had already maximized.
Compositor behavior and command-line options

New command line options have been added:

  • --no-cursor: hides the pointer.
  • --force-animations: forces animations to be enabled.
  • --enable-vt-switch: restores VT switching with the keyboard.

The --no-cursor option can be used to hide the pointer cursor entirely for setups where user input does not involve a pointing device (it is similar to the -nocursor option in Xorg).

Animations can now be disabled using the desktop settings, and will also be automatically disabled when the backend reports no hardware-accelerated rendering for performance purpose. The option --force-animations can be used to forcibly enable animations in that case, similar to GNOME Shell.

The native keybindings, which include VT switching keyboard shortcuts are now disabled by default for kiosk hardening. Applications that rely on the user being able to switch to another console VT on Linux, such as e.g Anaconda, will need to explicit re-enable VT switching using --enable-vt-switch in their session.

These options need to be passed from the command line starting gnome-kiosk, which would imply updating the systemd definitions files, or better, create a custom one (taking example on the the ones provided with the GNOME Kiosk sessions).

Accessibility Accessibility panel An example of an accessibility panel is now included, to control the platform accessibility settings with a GUI. It is a simple Python application using GTK4.

(The gsettings options are also documented in the CONFIG.md file.)

Screen magnifier

Desktop magnification is now implemented, using the same settings as the rest of the GNOME desktop (namely screen-magnifier-enabled, mag-factor, see the CONFIG.md file for details).

It can can be enabled from the accessibility panel or from the keyboard shortcuts through the gnome-settings-daemon’s “mediakeys” plugin.

Accessibility settings

The default systemd session units now start the gnome-settings-daemon accessibility plugin so that Orca (the screen reader) can be enabled through the dedicated keyboard shortcut.

Notifications
  • A new, optional notification daemon implements org.freedesktop.Notifications and org.gtk.Notifications using GTK 4 and libadwaita.
  • A small utility to send notifications via org.gtk.Notifications is also provided.
Input sources GNOME Kiosk was ported to the new Mutter’s keymap API which allows remote desktop servers to mirror the keyboard layout used on the client side. Session files and systemd
    • X-GDM-SessionRegister is now set to false in kiosk sessions as GNOME Kiosk does not register the session itself (unlike GNOME Shell). That fixes a hang when terminating the session.
    • Script session: systemd is no longer instructed to restart the session when the script exits, so that users can logout of the script session when the script terminates.

Matthew Garrett: Self hosting as much of my online presence as practical

Mër, 01/04/2026 - 4:35pd

Because I am bad at giving up on things, I’ve been running my own email server for over 20 years. Some of that time it’s been a PC at the end of a DSL line, some of that time it’s been a Mac Mini in a data centre, and some of that time it’s been a hosted VM. Last year I decided to bring it in house, and since then I’ve been gradually consolidating as much of the rest of my online presence as possible on it. I mentioned this on Mastodon and a couple of people asked for more details, so here we are.

First: my ISP doesn’t guarantee a static IPv4 unless I’m on a business plan and that seems like it’d cost a bunch more, so I’m doing what I described here: running a Wireguard link between a box that sits in a cupboard in my living room and the smallest OVH instance I can, with an additional IP address allocated to the VM and NATted over the VPN link. The practical outcome of this is that my home IP address is irrelevant and can change as much as it wants - my DNS points at the OVH IP, and traffic to that all ends up hitting my server.

The server itself is pretty uninteresting. It’s a refurbished HP EliteDesk which idles at 10W or so, along 2TB of NVMe and 32GB of RAM that I found under a pile of laptops in my office. We’re not talking rackmount Xeon levels of performance, but it’s entirely adequate for everything I’m doing here.

So. Let’s talk about the services I’m hosting.

Web

This one’s trivial. I’m not really hosting much of a website right now, but what there is is served via Apache with a Let’s Encrypt certificate. Nothing interesting at all here, other than the proxying that’s going to be relevant later.

Email

Inbound email is easy enough. I’m running Postfix with a pretty stock configuration, and my MX records point at me. The same Let’s Encrypt certificate is there for TLS delivery. I’m using Dovecot as an IMAP server (again with the same cert). You can find plenty of guides on setting this up.

Outbound email? That’s harder. I’m on a residential IP address, so if I send email directly nobody’s going to deliver it. Going via my OVH address isn’t going to be a lot better. I have a Google Workspace, so in the end I just made use of Google’s SMTP relay service. There’s various commerical alternatives available, I just chose this one because it didn’t cost me anything more than I’m already paying.

Blog

My blog is largely static content generated by Hugo. Comments are Remark42 running in a Docker container. If you don’t want to handle even that level of dynamic content you can use a third party comment provider like Disqus.

Mastodon

I’m deploying Mastodon pretty much along the lines of the upstream compose file. Apache is proxying /api/v1/streaming to the websocket provided by the streaming container and / to the actual Mastodon service. The only thing I tripped over for a while was the need to set the “X-Forwarded-Proto” header since otherwise you get stuck in a redirect loop of Mastodon receiving a request over http (because TLS termination is being done by the Apache proxy) and redirecting to https, except that’s where we just came from.

Mastodon is easily the heaviest part of all of this, using around 5GB of RAM and 60GB of disk for an instance with 3 users. This is more a point of principle than an especially good idea.

Bluesky

I’m arguably cheating here. Bluesky’s federation model is quite different to Mastodon - while running a Mastodon service implies running the webview and other infrastructure associated with it, Bluesky has split that into multiple parts. User data is stored on Personal Data Servers, then aggregated from those by Relays, and then displayed on Appviews. Third parties can run any of these, but a user’s actual posts are stored on a PDS. There are various reasons to run the others, for instance to implement alternative moderation policies, but if all you want is to ensure that you have control over your data, running a PDS is sufficient. I followed these instructions, other than using Apache as the frontend proxy rather than nginx, and it’s all been working fine since then. In terms of ensuring that my data remains under my control, it’s sufficient.

Backups

I’m using borgmatic, backing up to a local Synology NAS and also to my parents’ home (where I have another HP EliteDesk set up with an equivalent OVH IPv4 fronting setup). At some point I’ll check that I’m actually able to restore them.

Conclusion

Most of what I post is now stored on a system that’s happily living under a TV, but is available to the rest of the world just as visibly as if I used a hosted provider. Is this necessary? No. Does it improve my life? In no practical way. Does it generate additional complexity? Absolutely. Should you do it? Oh good heavens no. But you can, and once it’s working it largely just keeps working, and there’s a certain sense of comfort in knowing that my online presence is carefully contained in a small box making a gentle whirring noise.

Andy Wingo: wastrelly wabbits

Mar, 31/03/2026 - 10:34md

Good day! Today (tonight), some notes on the last couple months of Wastrel, my ahead-of-time WebAssembly compiler.

Back in the beginning of February, I showed Wastrel running programs that use garbage collection, using an embedded copy of the Whippet collector, specialized to the types present in the Wasm program. But, the two synthetic GC-using programs I tested on were just ported microbenchmarks, and didn’t reflect the output of any real toolchain.

In this cycle I worked on compiling the output from the Hoot Scheme-to-Wasm compiler. There were some interesting challenges!

bignums

When I originally wrote the Hoot compiler, it targetted the browser, which already has a bignum implementation in the form of BigInt, which I worked on back in the day. Hoot-generated Wasm files use host bigints via externref (though wrapped in structs to allow for hashing and identity).

In Wastrel, then, I implemented the imports that implement bignum operations: addition, multiplication, and so on. I did so using mini-gmp, a stripped-down implementation of the workhorse GNU multi-precision library. At some point if bignums become important, this gives me the option to link to the full GMP instead.

Bignums were the first managed data type in Wastrel that wasn’t defined as part of the Wasm module itself, instead hiding behind externref, so I had to add a facility to allocate type codes to these “host” data types. More types will come in time: weak maps, ephemerons, and so on.

I think bignums would be a great proposal for the Wasm standard, similar to stringref ideally (sniff!), possibly in an attenuated form.

exception handling

Hoot used to emit a pre-standardization form of exception handling, and hadn’t gotten around to updating to the newer version that was standardized last July. I updated Hoot to emit the newer kind of exceptions, as it was easier to implement them in Wastrel that way.

Some of the problems Chris Fallin contended with in Wasmtime don’t apply in the Wastrel case: since the set of instances is known at compile-time, we can statically allocate type codes for exception tags. Also, I didn’t really have to do the back-end: I can just use setjmp and longjmp.

This whole paragraph was meant to be a bit of an aside in which I briefly mentioned why just using setjmp was fine. Indeed, because Wastrel never re-uses a temporary, relying entirely on GCC to “re-use” the register / stack slot on our behalf, I had thought that I didn’t need to worry about the “volatile problem”. From the C99 specification:

[...] values of objects of automatic storage duration that are local to the function containing the invocation of the corresponding setjmp macro that do not have volatile-qualified type and have been changed between the setjmp invocation and longjmp call are indeterminate.

My thought was, though I might set a value between setjmp and longjmp, that would only be the case for values whose lifetime did not reach the longjmp (i.e., whose last possible use was before the jump). Wastrel didn’t introduce any such cases, so I was good.

However, I forgot about local.set: mutations of locals (ahem, objects of automatic storage duration) in the source Wasm file could run afoul of this rule. So, because of writing this blog post, I went back and did an analysis pass on each function to determine the set of locals which are mutated inside a try_block. Thank you, rubber duck readers!

bugs

Oh my goodness there were many bugs. Lacunae, if we are being generous; things not implemented quite right, which resulted in errors either when generating C or when compiling the C. The type-preserving translation strategy does seem to have borne fruit, in that I have spent very little time in GDB: once things compile, they work.

coevolution

Sometimes Hoot would use a browser facility where it was convenient, but for which in a better world we would just do our own thing. Such was the case for the number->string operation on floating-point numbers: we did something awful but expedient.

I didn’t have this facility in Wastrel, so instead we moved to do float-to-string conversions in Scheme. This turns out to have been a good test for bignums too; the algorithm we use is a bit dated and relies on bignums to do its thing. The move to Scheme also allows for printing floating-point numbers in other radices.

There are a few more Hoot patches that were inspired by Wastrel, about which more later; it has been good for both to work on the two at the same time.

tail calls

My plan for Wasm’s return_call and friends was to use the new musttail annotation for calls, which has been in clang for a while and was recently added to GCC. I was careful to limit the number of function parameters such that no call should require stack allocation, and therefore a compiler should have no reason to reject any particular tail call.

However, there were bugs. Funny ones, at first: attributes applying to a preceding label instead of the following call, or the need to insert if (1) before the tail call. More dire ones, in which tail callers inlined into their callees would cause the tail calls to fail, worked around with judicious application of noinline. Thanks to GCC’s Andrew Pinski for help debugging these and other issues; with GCC things are fine now.

I did have to change the code I emitted to return “top types only”: if you have a function returning type T, you can tail-call a function returning U if U is a subtype of T, but there is no nice way to encode this into the C type system. Instead, we return the top type of T (or U, it’s the same), e.g. anyref, and insert downcasts at call sites to recover the precise types. Not so nice, but it’s what we got.

Trying tail calls on clang, I ran into a funny restriction: clang not only requires that return types match, but requires that tail caller and tail callee have the same parameters as well. I can see why they did this (it requires no stack shuffling and thus such a tail call is always possible, even with 500 arguments), but it’s not the design point that I need. Fortunately there are discussions about moving to a different constraint.

scale

I spent way more time that I had planned to on improving the speed of Wastrel itself. My initial idea was to just emit one big C file, and that would provide the maximum possibility for GCC to just go and do its thing: it can see everything, everything is static, there are loads of always_inline helpers that should compile away to single instructions, that sort of thing. But, this doesn’t scale, in a few ways.

In the first obvious way, consider whitequark’s llvm.wasm. This is all of LLVM in one 70 megabyte Wasm file. Wastrel made a huuuuuuge C file, then GCC chugged on it forever; 80 minutes at -O1, and I wasn’t aiming for -O1.

I realized that in many ways, GCC wasn’t designed to be a compiler target. The shape of code that one might emit from a Wasm-to-C compiler like Wastrel is different from that that one would write by hand. I even ran into a segfault compiling with -Wall, because GCC accidentally recursed instead of iterated in the -Winfinite-recursion pass.

So, I dealt with this in a few ways. After many hours spent pleading and bargaining with different -O options, I bit the bullet and made Wastrel emit multiple C files. It will compute a DAG forest of all the functions in a module, where edges are direct calls, and go through that forest, greedily consuming (and possibly splitting) subtrees until we have “enough” code to split out a partition, as measured by number of Wasm instructions. They say that -flto makes this a fine approach, but one never knows when a translation unit boundary will turn out to be important. I compute needed symbol visibilities as much as I can so as to declare functions that don’t escape their compilation unit as static; who knows if this is of value. Anyway, this partitioning introduced no performance regression in my limited tests so far, and compiles are much much much faster.

scale, bis

A brief observation: Wastrel used to emit indented code, because it could, and what does it matter, anyway. However, consider Wasm’s br_table: it takes an array of n labels and an integer operand, and will branch to the nth label, or the last if the operand is out of range. To set up a label in Wasm, you make a block, of which there are a handful of kinds; the label is visible in the block, and for n labels, the br_table will be the most nested expression in the n nested blocks.

Now consider that block indentation is proportional to n. This means, the file size of an indented C file is quadratic in the number of branch targets of the br_table.

Yes, this actually bit me; there are br_table instances with tens of thousands of targets. No, wastrel does not indent any more.

scale, ter

Right now, the long pole in Wastrel is the compile-to-C phase; the C-to-native phase parallelises very well and is less of an issue. So, one might think: OK, you have partitioned the functions in this Wasm module into a number of files, why not emit the files in parallel?

I gave this a go. It did not speed up C generation. From my cursory investigations, I think this is because the bottleneck is garbage collection in Wastrel itself; Wastrel is written in Guile, and Guile still uses the Boehm-Demers-Weiser collector, which does not parallelize well for multiple mutators. It’s terrible but I ripped out parallelization and things are fine. Someone on Mastodon suggested fork; they’re not wrong, but also not Right either. I’ll just keep this as a nice test case for the Guile-on-Whippet branch I want to poke later this year.

scale, quator

Finally, I had another realization: GCC was having trouble compiling the C that Wastrel emitted, because Hoot had emitted bad WebAssembly. Not bad as in “invalid”; rather, “not good”.

There were two cases in which Hoot emitted ginormous (technical term) functions. One, for an odd debugging feature: Hoot does a CPS transform on its code, and allocates return continuations on a stack. This is a gnarly technique but it gets us delimited continuations and all that goodness even before stack switching has landed, so it’s here for now. It also gives us a reified return stack of funcref values, which lets us print Scheme-level backtraces.

Or it would, if we could associate data with a funcref. Unfortunately func is not a subtype of eq, so we can’t. Unless... we pass the funcref out to the embedder (e.g. JavaScript), and the embedder checks the funcref for equality (e.g. using ===); then we can map a funcref to an index, and use that index to map to other properties.

How to pass that funcref/index map to the host? When I initially wrote Hoot, I didn’t want to just, you know, put the funcrefs of interet into a table and let the index of a function’s slot be the value in the key-value mapping; that would be useless memory usage. Instead, we emitted functions that took an integer, and which would return a funcref. Yes, these used br_table, and yes, there could be tens of thousands of cases, depending on what you were compiling.

Then to map the integer index to, say, a function name, likewise I didn’t want a table; that would force eager allocation of all strings. Instead I emitted a function with a br_table whose branches would return string.const values.

Except, of course, stringref didn’t become a thing, and so instead we would end up lowering to allocate string constants as globals.

Except, of course, Wasm’s idea of what a “constant” is is quite restricted, so we have a pass that moves non-constant global initializers to the “start” function. This results in an enormous start function. The straightforward solution was to partition global initializations into separate functions, called by the start function.

For the funcref debugging, the solution was more intricate: firstly, we represent the funcref-to-index mapping just as a table. It’s fine. Then for the side table mapping indices to function names and sources, we emit DWARF, and attach a special attribute to each “introspectable” function. In this way, reading the DWARF sequentially, we reconstruct a mapping from index to DWARF entry, and thus to a byte range in the Wasm code section, and thus to source information in the .debug_line section. It sounds gnarly but Guile already used DWARF as its own debugging representation; switching to emit it in Hoot was not a huge deal, and as we only need to consume the DWARF that we emit, we only needed some 400 lines of JS for the web/node run-time support code.

This switch to data instead of code removed the last really long pole from the GCC part of Wastrel’s pipeline. What’s more, Wastrel can now implement the code_name and code_source imports for Hoot programs ahead of time: it can parse the DWARF at compile-time, and generate functions that look up functions by address in a sorted array to return their names and source locations. As of today, this works!

fin

There are still a few things that Hoot wants from a host that Wastrel has stubbed out: weak refs and so on. I’ll get to this soon; my goal is a proper Scheme REPL. Today’s note is a waypoint on the journey. Until next time, happy hacking!

Thibault Martin: TIL that Sveltia is a good CMS for Astro

Mar, 31/03/2026 - 11:00pd

This website is built with the static site generator Astro. All my content is written in markdown and uploaded to a git repository. Once the content is merged into the main branch, Cloudflare deploys it publicly. The process to publish involves:

  1. Creating a new markdown file.
  2. Filling it with thoughts.
  3. Pushing it to a new branch.
  4. Waiting for CI to check my content respects some rules.
  5. Pressing the merge button.

This is pretty involved and of course requires access to a computer. This goes directly against the goal I’ve set for myself to reduce friction to publish.

I wanted a simple solution to write and publish short posts directly from mobile, without hosting an additional service.

Such an app is called a git-based headless CMS. Decap CMS is the most frequently cited solution for git-based content management, but it has two show-stoppers for me:

  1. It’s not mobile friendly (yet, since 2017) although there are community workarounds.
  2. It’s not entirely client-side. You need to host a serverless script e.g. on a Cloudflare Worker to complete authentication.

Because my website is completely static, it’s easy to take it off GitHub and Cloudflare and move it elsewhere. I want the CMS solution I choose to be purely client-side, so it doesn’t get in the way of moving elsewhere.

It turns out that Sveltia, an API-compatible and self-proclaimed successor to Decap, is a good fit for this job, with a few caveats.

Sveltia is a mobile-friendly Progressive Web App (PWA) that doesn’t require a backend. It's a static app that can be added to my static website. It has a simple configuration file to describe what fields each post expects (title, publication date, body, etc).

Once the configuration and authentication are done, I have access to a lightweight PWA that lets me create new posts.

The authentication is straightforward for technical people. I need to paste a GitHub Personal Access Token (PAT) in the login page, and that's it. Sveltia will fetch the existing content and display it.

The PWA itself is also easy to deploy: I need to add a page served under the /admin route, that imports the app. I could just import it from a third party CDN, but there’s also a npm package for it. It allows me to serve the javascript as a first party instead, all while easily staying up to date.

I installed it with

$ pnpm add @sveltia/cms

I then created an Astro page under src/pages/admin/index.astro with the following content

title="src/pages/admin/index.astro" <!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" /> <title>Content Manager – ergaster.org</title> <script> import { init } from "@sveltia/cms"; init(); </script> </head> <body></body> </html>

I also created the config file under public/admin/config.yml with Sveltia Entry Collections matching my Astro content collections. The setup is straightforward and well documented.

Sveltia has a few caveats though:

  1. It can only work on a single branch, and not create a new branch per post. According to the maintainer, it should be possible to create new branches with “Editorial Workflowby Q2 or Q3 this year.
  2. It pushes content directly to its target branch, including drafts. I still want to run CI checks before merging my content, so I’ve created a drafts branch and configured Sveltia to push content there. Once the CI checks have passed I merge the branch manually from the GitHub mobile app.
  3. Having a single target branch also means I can only have one draft coming from Sveltia at a time. If I edited two drafts concurrently on the drafts branch, they would both be published the next time I merged drafts into main.
  4. It’s clunky to rename a picture uploaded via Sveltia.

Those are not deal breakers to me. The maintainer seems reactive, and the Editorial Workflow feature coming in Q2 or Q3 will fix the remaining clunkiness.

Thibault Martin: Gear review: Garmin Forerunner 165

Hën, 30/03/2026 - 2:00pd

Last year I bought one of those rock solid, simple, sturdy Casio Watches that are supposed to last you a long time. I still love it with all my heart and sometimes wear it, but my primary watch is now the Garmin Forerunner 165.

A dozen years ago I got into running and installed an app on my phone to track my progress. I kept pushing harder to beat my previous records, and eventually injured myself badly. I used to think that the quantified self was the root of all evil and that it was the reason why I overtrained. It actually was out of ego, and it turns out that quantified self and coaching to make sense of the data can yield amazing results.

[!success] I got a Garmin Forerunner 165 and I am very happy with it.

I recommend caution nonetheless: the watch is a great tool to gather metrics about how you run, but it is a terrible replacement for a coach. Working with a professional will give you much better results.

Why I got it

In my mid 30s I realized that my metabolism wasn't what it used to be. I started gaining weight and felt uneasy in my body. After two kids and today's geopolitics, my mental health started to degrade too.

I've decided to get back into running to help with both aspects. Exercising makes the body burn calories and produce endorphins. Both are useful to feel better. Yes, it's only in my mid 30s that I realized that exercising was a physiological need. A need I had neglected for too long.

But the last time I got into running I injured myself badly. I'm the least competitive person against others, but I'm very competitive against myself. This means I'm subject to overtraining. I needed something to keep me on track.

Several friends had Garmin watches and told me that their watches actually prevented them from overtraining. That was my cue: I would buy one, and use it to get back in shape. Even better: the model I wanted, the Forerunner 165, could last 11 days on a single charge. It means I could wear it at night to follow my sleep patterns, and it would vibrate gently on my wrist to wake me up silently. This promised to be a low maintenance and useful watch.

[!info] In summary

I bought the Forerunner 165 to:

  • Get back into running.
  • Prevent overtraining.
  • Follow my sleep patterns.
  • Not babysit the watch or be constantly nagged by it.
First impressions

I bought the watch at the end of August 2025, and I started running immediately with it. In addition to the watch, I also bought a pair of Merrell Trail Glove 7: "barefoot" shoes that have such a thin sole that you have to land on the front of the foot and not on the heel.

I installed the Garmin Connect app on my phone, and I was delighted to see that I didn't any subscription to start a coaching plan. I enrolled in a Garmin Coach program for beginners, and I started following it. At the end of each run, the watch would ask me how I felt, and each run was more painful than the last. I thought it was just muscles building up so I kept following the program. And this is how I injured myself.

I went to a physiotherapist, and we started a specific training plan for barefoot shoes. Of course I quit the Garmin Coach one. He taught me that barefoot shoes require a higher cadence (number of steps per minute) than regular running shoes. I could configure the watch to keep track of my cadence during my runs. A gauge would tell me if I ran too few or too many steps per minute.

After the physiotherapy sessions ended, I could keep running normally. As of writing, I go running three times a week. Each session is between 6 and 12km long.

What I like

I didn't want a smart watch because I don't want it to pester me, and I don't want to charge it every day. On those two fronts the Forerunner delivered. I don't receive any of my phone notifications on my watch, and it doesn't pester me with anything during the day. I use my watch when I need it, not when it needs me.

As for the battery, it is fantastic for this type of watch. With 3 runs a week, my watch lasts 9 to 10 days on a single charge. I keep my watch at night so it monitors my sleep too. And charging is fast: I can charge it from 10 to 100% in about 1.5 hour.

I can confidently go to sleep with it and know it will still have plenty of battery to wake me up the next day with a gentle vibration on my wrist. I hate alarms that scream at you in the morning. This gentle nudge is infinitely better.

I have pathologically bad sleep and the watch does a good job at tracking it. It even helped me detect sleep apnea, that doctors later confirmed. The watch gives you several metrics for the night: how long you've slept, your average and resting heart rate, your average and lowest respiration and more. It can also measure your pulse oximetry, but that depletes the battery twice as fast as if it doesn't. That watch also supports tracking naps.

When it comes to exercising, I only use it for running. I can't say anything about its accuracy, but my physiotherapist seemed to believe that all the measures were plausible.

The wristband has many holes, making it easy to adjust. It is comfortable to wear, even during exercise when the wrist can swell and sweat a bit. It is also slightly elastic, so it can stretch a bit for extra comfort.

It is possible to use the watch only to track how you run, or to configure workouts in the app depending on your objectives. During my physiotherapy training I would make it track my cadence, but you can track a lot more metrics. You can also configure several steps, e.g. warm-up, light run, fast run, series, etc.

There are also built-in, free Garmin Coach programs depending on your needs, but I can't say I have a positive experience with them. If you're new to running I really recommend going to a professional coach or physiotherapist to get you started.

At €230, the watch is not cheap, but seems fairly priced for the amount of value I get from it. I also don't expect to replace it anytime soon.

What I don't like

The watch has an odd recovery time metric can be difficult to understand: you can workout lightly to recover. To this day I'm not entirely sure what it does.

Beyond that it's a good watch I can't complain about!

Conclusion

I’m very happy with my Forerunner 165. It’s important to bear in mind it’s just a tool, not something that can replace a human coach. If your knees or tendons hurt and the watch tells you to go running, don’t. Go see a professional.

Thibault Martin: I realized that You don't care

Dje, 29/03/2026 - 6:00md

Quite a few of us maintain our own websites and publish our thoughts. We play in hard mode:

  • We need to build our website before even publishing our first post.
  • We don’t benefit from the network effect of bigger platforms to get eyeballs on our writing.
  • LLMs aggressively scrape the web and can serve our thoughts or expertise to their users without them visiting our websites.

And on top of that, you don’t care.

And I don’t expect you to care. Like the rest of us, you are flooded with information constantly. You’re fed so many words that you read the equivalent of whole books every day. How entitled would I be to expect you to care about my words when you have to filter through every story you’re bombarded with.

So why do we keep the small web alive?

I can’t speak for others, but I know why I maintain my website and why I publish my thoughts there. By increasing order of importance:

  1. I keep my web development skills reasonably up to date.
  2. I can shape my website to adapt to my content, and not the other way around.
  3. I have freedom of tone and vocabulary. I don’t have to censor words like "suicide" or "sex".
  4. I write long form posts that help me shape my thoughts, develop ideas, and receive feedback from my peers and readers.

If you can afford to, I can only encourage you to write and publish your thoughts on your own platform, as long as you don’t expect others to care in return.

Gedit Technology: gedit 50.0 released

Sht, 28/03/2026 - 11:00pd

gedit 50.0 has been released! Here are the highlights since version 49.0 from January. (Some sections are a bit technical).

No Large Language Models AI tools

The gedit project now disallows the use of LLMs for contributions.

The rationales:

Programming can be seen as a discipline between art and engineering. Both art and engineering require practice. It's the action of doing - modifying the code - that permits a deep understanding of it, to ensure correctness and quality.

When generating source code with an LLM tool, the real sources are the inputs given to it: the training dataset, plus the human commands.

Adding something generated to the version control system (e.g., Git) is usually frown upon. Moreover, we aim for reproducible results (to follow the best-practices of reproducible builds, and reproducible science more generally). Modifying afterwards something generated is also a bad practice.

Releasing earlier, releasing more often

To follow more closely the release early, release often mantra, gedit aims for a faster release cadence in 2026, to have smaller deltas between each version. Future will tell how it goes.

The website is now responsive

Since last time, we've made some efforts to the website. Small-screen-device readers should have a more pleasant experience.

libgedit-amtk becomes "The Good Morning Toolkit"

Amtk originally stands for "Actions, Menus and Toolbars Kit". There was a desire to expand it to include other GTK extras that are useful for gedit needs.

A more appropriate name would be libgedit-gtk-extras. But renaming the module - not to mention the project namespace - is more work. So we've chosen to simply continue with the name Amtk, just changing its scope and definition. And - while at it - sprinkle a bit of fun :-)

So there are now four libgedit-* modules:

  • libgedit-gfls, aka "libgedit-glib-extras", currently for "File Loading and Saving";
  • libgedit-amtk, aka "libgedit-gtk-extras" - it extends GTK for gedit needs at the exception of GtkTextView;
  • libgedit-gtksourceview - it extends GtkTextView and is a fork of GtkSourceView, to evolve the library for gedit needs;
  • libgedit-tepl - the Text Editor Product Line library, it provides a high-level API, including an application framework for creating more easily new text editors.

Note that all of these are still constantly in construction.

Some code overhaul

Work continues steadily inside libgedit-gfls and libgedit-gtksourceview to streamline document loading.

You might think that it's a problem solved (for many years), but it's actually not the case for gedit. Many improvements are still possible.

Another area of interest is the completion framework (part of libgedit-gtksourceview), where changes are still needed to make it fully functional under Wayland. The popup windows are sometimes misplaced. So between gedit 49.0 and 50.0 some progress has been made on this. The Word Completion gedit plugin works fine under Wayland, while the LaTeX completion with Enter TeX is still buggy since it uses more features from the completion system.

Thibault Martin: I realized that I created too much friction to publish

Sht, 28/03/2026 - 11:00pd

I love writing on my blog. I love taking a complex topic, breaking it down, understanding how things work, and writing about how things clicked for me. It serves a double purpose:

  1. I can organize my thoughts, ensure I understood the topic fully, and explain it to others.
  2. It helps my future self: if I forgot about the topic, I can read about what made it click for me.

But as of writing, the last time I published something on my blog was 5 months ago.

The blogging process

My blog posts tend to be lengthy. My writing and publishing process is the following.

  1. Take a nontrivial topic, something I didn't know about or didn't know how to do.
  2. Understand it, break it down, and get a clear picture of how things work.
  3. Write an outline for the post with the key points.
  4. Ask my smarter friends if the outline makes sense.
  5. Flesh out the outline into a proper blog posts, with all the details, code snippets, screenshots.
  6. Ask my smarter friends to review the post again.
  7. Get an illustrator to create a banner for the post, that also serves as an opengraph preview image.
  8. Publish the post.

That is a lot of work. I have many posts stuck between step 3 and 5, because they take quite a bit of time. Asking an illustrator to create a banner for the post also creates more friction: obviously I need to pay the illustrator, but I also need to wait for him to be done with the illustration.

Not everything has to be a blog post

Sometimes I have quick thoughts that I want to jot down and share with the rest of the world, and I want to be able to find it back. There are two people I follow that write a lot, often in short format.

  1. John Gruber on his blog Daring Fireball.
  2. Simon Willison, on his Weblog.

Both of them have very short format notes. Willison even blogged about what he thinks people should write about.

Reducing friction and just posting

I don't think friction should be avoided at all costs. Take emails for example: there's a delay between when you send a message and your peer receives it, or the other way around. That friction encourages longer form messages, which gives more time to organize thoughts.

I also welcome the friction I have created for my own posts: I get through a proper review process and publish higher quality posts.

But there's also room for spontaneity. So I've updated my website to let me publish two smaller formats:

  • TILs. Those are short posts about something I've learned and found interesting.
  • Thoughts. Those are shorter posts I jot down in less than 20 minutes to develop simple thoughts.

Sebastian Wick: Three Little Rust Crates

Pre, 27/03/2026 - 1:15pd

I published three Rust crates:

  • name-to-handle-at: Safe, low-level Rust bindings for Linux name_to_handle_at and open_by_handle_at system calls
  • pidfd-util: Safe Rust wrapper for Linux process file descriptors (pidfd)
  • listen-fds: A Rust library for handling systemd socket activation

They might seem like rather arbitrary, unconnected things – but there is a connection!

systemd socket activation passes file descriptors and a bit of metadata as environment variables to the activated process. If the activated process exec’s another program, the file descriptors get passed along because they are not CLOEXEC. If that process then picks them up, things could go very wrong. So, the activated process is supposed to mark the file descriptors CLOEXEC, and unset the socket activation environment variables. If a process doesn’t do this for whatever reason however, the same problems can arise. So there is another mechanism to help prevent it: another bit of metadata contains the PID of the target. Processes can check it against their own PID to figure out if they were the target of the activation, without having to depend on all other processes doing the right thing.

PIDs however are racy because they wrap around pretty fast, and that’s why nowadays we have pidfds. They are file descriptors which act as a stable handle to a process and avoid the ID wrap-around issue. Socket activation with systemd nowadays also passes a pidfd ID. A pidfd ID however is not the same as a pidfd file descriptor! It is the 64 bit inode of the pidfd file descriptor on the pidfd filesystem. This has the advantage that systemd doesn’t have to install another file descriptor in the target process which might not get closed. It can just put the pidfd ID number into the $LISTEN_PIDFDID environment variable.

Getting the inode of a file descriptor doesn’t sound hard. fstat(2) fills out struct stat which has the st_ino field. The problem is that it has a type of ino_t, which is 32 bits on some systems so we might end up with a process identifier which wraps around pretty fast again.

We can however use the name_to_handle syscall on the pidfd to get a struct file_handle with a f_handle field. The man page helpfully says that “the caller should treat the file_handle structure as an opaque data type”. We’re going to ignore that, though, because at least on the pidfd filesystem, the first 64 bits are the 64 bit inode. With systemd already depending on this and the kernel rule of “don’t break user-space”, this is now API, no matter what the man page tells you.

So there you have it. It’s all connected.

Obviously both pidfds and name_to_handle have more exciting uses, many of which serve my broader goal: making Varlink services a first-class citizen. More about that another time.

Andy Wingo: free trade and the left, quater: witches

Enj, 26/03/2026 - 11:03md

Good evening. Tonight, we wrap up our series on free trade and the left. To recap where we were, I started by retelling the story that free trade improves overall productivity, but expressed reserves about the way in which it does so: plant closures and threats thereof, regulatory arbitrage, and so on. Then we went back in history, discussing the progressive roots of free trade as a cause of the peace-and-justice crowd, in the 19th century. Then we looked at the leading exponents of free trade in the 20th century, the neoliberals , ending in an odd place: instead of free trade being a means for the end of peace and prosperity, neoliberalism turns this on its head, instead holding that war, immiseration, apartheid, dictatorship, ecological disaster, all are justified if they serve the ends of the “free market”, of which free trade is a component.

When I make this list of evils I find myself back in 1999, that clearly “we” were right then to shut down the WTO meetings in Seattle. With the distance of time, I start to wonder, not about then, but about now: for all the evil of our days, Trump at least has the virtue of making clear that trade barriers have a positive dot-product with acts of war. As someone who lives in the banlieue of Geneva, I am always amused when I find myself tut-tutting over the defunding of this or that institution of international collaboration.

I started this series by calling out four works. Pax Economica and Globalists have had adequate treatment. The third, Webs of Power, by Starhawk, is one that I have long seen as a bit of an oddball; forgive my normie white boy (derogatory) sensibilities, but I have often wondered how a book by a voice of “earth-based spirituality and Goddess religion” has ended up on my shelf. I am an atheist. How much woo is allowed to me?

choice of axiom

Conventional wisdom is to treat economists seriously, and Wiccans less so. In this instance, I have my doubts. The issue is that a neoliberal is at the same time a true believer in markets, and a skilled jurist. In service of the belief, any rhetorical device is permissible, if it works; if someone comes now and tries to tell me that the EU-Mercosur agreement is a good thing because of its effect on capybara populations, my first reaction is to doubt them, because maybe they are a neoliberal, and if so they would literally say anything.

Whereas if Starhawk has this Earth-mother-spiritual vibe... who am I to say? Yes, I think religion on the whole is a predatory force on vulnerable people, but that doesn’t mean that her interpretation of the web of life as divine is any less legitimate than neoliberal awe of the market. Let’s hear her argument and get on with things.

Starhawk’s book has three parts. The first is an as-I-lived-it chronicle, going from Seattle to Washington to Prague to Quebec City to Genoa, and thence to 9/11 and its aftermath, describing what it was like to be an activist seeking to disrupt the various WTO-adjacent meetings, seeking to build something else. She follows this up with 80 pages of contemporary-to-2002 topics such as hierarchy within the movement, nonviolence vs black blocs, ecological principles, cultural appropriation, and so on.

These first two sections inform the last final 20 pages, in which Starhawk attempts to synthesize what it is that “we” wanted, as a kind of memento and hopefully a generator of actions to come. She comes up with a list of nine principles, which I’ll just quote here because I don’t have an editor (the joke’s on all of us!):

  1. We must protect the viability of the life-sustaining systems of the planet, which are everywhere under attack.
  2. A realm of the sacred exists, of things too precious to be commodified, and must be respected.
  3. Communities must control their own resources and destinies.
  4. The rights and heritages of indigenous communities must be acknowledged and respected.
  5. Enterprises must be rooted in communities and be responsible to communities and to future generations.
  6. Opportunity for human beings to meet their needs and fulfill their dreams and aspirations should be open to all.
  7. Labor deserves just compensation, security, and dignity.
  8. The human community has a collective responsibility to assure the basic means of life, growth, and development for all its members.
  9. Democracy means that all people have a voice in the decisions that affect them, including economic decisions.

Now friends, this is Starhawk’s list, not mine, and a quarter-century-old list at that. I’m not here to judge it, though I think it’s not bad; what I find interesting is its multifaceted nature, that when contrasted with the cybernetic awe of late neoliberalism, that actually it’s the Witch who has the more down-to-earth concerns: a planet to live on, a Rawlsian concern with justice, and a control of the economic by the people.

which leaves us

Former European Central Bank president Mario Draghi published a report some 18 months ago diagnosing a European malaise and proposing a number of specific remedies. I find that we on my part of the left are oft ill-equipped to engage with the problem he identifies, not to mention the solutions. The whole question of productivity is very technical, to the extent that we might consider it owned by our enemies: our instinct is to deflect, “productivity for what”, that sort of thing. Worse, if we do concede the problem, we haven’t spent as much time sparring in the gyms of comparative advantage; we risk a first-round knockout. We come with Starhawk’s list in hand, and they smile at us condescendingly: “very nice but we need to focus on the economy, you know,” and we lose again.

But Starhawk was not wrong. We do need a set of principles that we can use to analyze the present and plot a course to the future. I do not pretend to offer such a set today, but after having looked into the free trade question over the last couple months, I have reached two simple conclusions, which I will share with you now.

The first is that, from an intellectual point of view, we should just ignore the neoliberals; they are not serious people. That’s not a value judgment on the price mechanism, but rather one on those that value nothing else: that whereas classical liberalism was a means to an end, neoliberalism admits no other end than commerce, and admits any means that furthers its end. And so, we can just ignore them. If neoliberals were the only ones thinking about productivity, well, we might need new branches of economics. Fortunately that’s not the case. Productivity is but one dimension of the good, and it is our collective political task to choose a point from the space of the possible according to our collective desires.

The second conclusion is that we should take back free trade from our enemies on the right. We are one people, but divided into states by historical accident. Although there is a productivity argument for trade, we don’t have to limit ourselves to it: the bond that one might feel between Colorado and Wyoming should be the same between Italy and Tunisia, between Canada and Mexico, indeed between France and Brasil. One people, differentiated but together, sharing ideas and, yes, things. Internationalism, not nationalism.

There is no reason to treat free trade as the sole criterion against which to judge a policy. States are heterogeneous: what works for the US might not be right for Haiti; states differ in the degree that they internalize environmental impacts; and they differ as regards public services. We can take these into account via policy, but our goal should be progress for all.

So while Thomas Piketty is right to decry a kind of absolutism among European decisionmakers regarding free trade, I can’t help but notice a chauvinist division being set up in the way we leftists are inclined to treat these questions: we in Europe are one bloc, despite e.g. very different carbon impacts of producing a dishwasher in Poland versus Spain, whereas a dishwasher from China belongs to a different, worse, more sinful category.

and mercosur?

To paraphrase Marley’s ghost, mankind is my business. I want an ever closer union with my brothers and sisters in Uruguay and Zambia and Cambodia and Palestine. Trade is a part of it. All things being equal, we should want to trade with Chile. We on the left should not oppose free trade with Mercosur out of a principle that goods produced far away are necessarily a bad thing.

All this is not to say that we should just doux it (although, gosh, Karthik is such a worthy foe); we can still participate in collective carrot-and-stick exercises such as carbon taxes and the like, and this appreciation of free trade would not have trumped the campaign to boycott apartheid South Africa, nor would it for apartheid Israel. But our default position should be to support free trade with Mercosur, in such a way that does improves the lot of all humanity.

I don’t know what to think about the concrete elements of the EU-Mercosur deal. The neoliberal play is to design legal structures that encase commerce, and a free trade deal risks subordinating the political to the economic. But unlike some of my comrades on the left, I am starting to think that we should want free trade with Bolivia, and that’s already quite a change from where I was 25 years ago.

fin

Emily Saliers famously went seeking clarity; I fear I have brought little. We are still firmly in the world of the political, and like Starhawk, still need a framework of pre-thunk thoughts to orient us when some Draghi comes with a new four-score-page manifesto. Good luck and godspeed.

But it is easier to find a solution if we cull the dimensionality of the problem. The neoliberals had their day, but perhaps these staves may be of use to you in exorcising their discursive domination; it is time we cut them off. Internationalist trade was ours anyway, and it should resume its place as a means to our ends.

And what ends? As with prices, we discover them on the margin, in each political choice we make. Some are easy; some less so. And while a list like Starhawk’s is fine enough, I keep coming back to a simpler question: which side are you on? The sheriff or the union? ICE or the immigrant? Which side are you on? The question cuts fine. For the WTO in Seattle, to me it said to shut it all down. For EU-Mercosur, to me it says, “let’s talk.”

Thibault Martin: TIL that Proxmox can provision Kubernetes Persistent Volumes

Mër, 25/03/2026 - 11:00pd

I wanted to dip my toes into Kubernetes for my homelab, but I knew I would need some flexibility to experiment. So instead of deploying k3s directly on my server, I

  1. Installed a base Debian on my server, encrypting the disk with LUKS and using LVM to partition it.
  2. Installed the Proxmox hypervisor on that base Debian
  3. Spun up a Debian VM, and installed k3s on it.

Proxmox supports several storage plugins. It allows me to create LVM Local Volumes for the VM disks for example.

This setup allows me to spin up fresh VMs for my experiments, all while leaving my production k3s intact. This is great, but it came up with two problems:

  1. When I provision the VM for k3s I need to allocate it a massive amount of disk space. This is because k3s uses a local path provisioner to provision new Persistent Volumes directly on the VM.
  2. I can't take snapshots of the Persistent Volumes when doing backups. There's a risk that the data will change while I perform the backup.

The situation looks like the following.

On the LVM disk of the host, I create a VM for k3s. This VM has a virtual disk that doesn't rely on LVM, so it can't create LVM Logical Volumes. The local provisioner can only create volumes on the virtual disk, because it can't escape the VM to create volumes on the Proxmox host.

Because the volumes are created on the virtual disk that doesn't rely on LVM, I can't use LVM snapshots to take snapshots of my volumes.

[!question] Why not LVM Thin?

One solution to address the massive disk requirement could be to use LVM Thin: it would allow me to allocate a lot of space in theory, but in practice in only fills up as the VM storage gets used.

I don't want to use LVM Thin because it puts me at risk of overprovisioning. I could allocate more storage than I actually have, and it would be difficult to realize that my disks are filling up before it's too late.

My colleague Quentin mentioned the Proxmox CSI Plugin. It is a plugin that replaces k3s' local path provisioner. Instead of creating the kubernetes Persistent Volumes inside the VM, it calls the Proxmox host, asks it to create a LVM Logical Volume and binds it to a Persistent Volume in kubernetes.

Using the Proxmox CSI volume, the situation would look like this.

It solves the two problems for me:

  1. I can now only provision a small disk for the k3s VM, since the Persistent Volumes will be created outside of the VM.
  2. Since Proxmox will create LVM Logical Volumes to provision the Persistent Volumes, I can either do a LVM Snapshot from Proxmox or use Kubernete's Volume Snapshot feature, with some caveats.

Setting up the Proxmox-CSI-Plugin for k3s can be a bit involved, but I'm writing a longer blog post about it.

Thibault Martin: TIL that GNOME has launched a fellowship program

Mar, 24/03/2026 - 8:00md

When open source nonprofits ask for donations, one common answer is "I only want to fund code, I don't want to fund anything else." GNOME has created a Fellowship Program to fund direct work on GNOME, a program entirely funded by donations. This is a testament to the Foundation's maturity, as it becomes a direct contributor to the project it stewards.

Let's take a step back to address the code-only argument. It is a misguided reaction, but I can see where its proponents are coming from. In the world of proprietary software, you pay to get your software. You don't realize that this bundles the marketing, accounting, legal, and even HR costs.

In the open source world, everyone can see who contributes code and how that code is built and packaged to create a software solution. A lot of things are not shown in git commits though. A few of them are:

  • What did it take to create the Human Interface Guidelines to have a coherent suite of applications? How many designers had to meet, what research did they have to do, did they have to meet in person?
  • What did it take to create the Developer Documentation to onboard new developers, help them make their first steps, and turn them into bigger contributors over the years?
  • What did it take to build a website to advertize all the cool apps that follow the GNOME HIG?
  • What did it take to set up the infrastructure the code lives on, and that builds the software we all love?

GNOME, like many other open source projects, is first and foremost a community. This is a group of people with diverse backgrounds, diverse opinions, who try to find common ground to solve problems. They don't always agree on how to solve problems, nor necessarily on what even is a problem in the first place.

The role of The GNOME Foundation is to provide a place to support its community. Its role is to help its contributors find common ground. Its role is to give them the tools and opportunities to do so.

Some people still don't value this, and want The GNOME Foundation to be a vendor for GNOME. They want to fund developers to produce code, because that's a very visible metric.

For them, and for everyone who's ever wanted to give back to GNOME without knowing how, The GNOME Foundation has created a Fellowship Program. It will directly fund a person to work on what few people want to do in their spare time: maintenance.

Round one focuses on sustainability: improving tooling, build systems, test infrastructure, automation, documentation, developer productivity, and ongoing maintainability. We are not funding feature development: the goal is for each fellowship to leave the project in a more efficient and sustainable state.

This is only fueled by our donations. If you want a direct pipeline between your money and GNOME development, this is it. Donate to GNOME, we can't afford not to have them when Big Tech has so much influence on our lives.