Author Archives: Lorenzo Bettini

About Lorenzo Bettini

Lorenzo Bettini is an Associate Professor in Computer Science at the Dipartimento di Statistica, Informatica, Applicazioni "Giuseppe Parenti", Università di Firenze, Italy. Previously, he was a researcher in Computer Science at Dipartimento di Informatica, Università di Torino, Italy. He has a Masters Degree summa cum laude in Computer Science (Università di Firenze) and a PhD in "Logics and Theoretical Computer Science" (Università di Siena). His research interests cover design, theory, and the implementation of statically typed programming languages and Domain Specific Languages. He is also the author of about 90 research papers published in international conferences and international journals.

Running Homebrew Commands with sudo on Linux and macOS

Sometimes a command installed with Homebrew needs to be executed as root. A good example is nethogs, which needs elevated privileges to inspect network traffic.

The problem is that this often fails:

even though this works:

The reason is that sudo usually runs with a different, restricted PATH. Your normal shell can find Homebrew commands, but sudo may not know where Homebrew installed them.

On Linux, Homebrew commands may live somewhere like:

On macOS, they are commonly under:

on Apple Silicon, or:

on Intel Macs.

The simple one-command solution

A quick workaround is to resolve the command path before calling sudo:

This works because which nethogs is evaluated by your normal shell, using your normal PATH, before sudo runs.

So instead of asking sudo to find nethogs, you give it the full path directly.

A shell function for one command

You can wrap that in a function:

This lets you run:

and still preserve arguments correctly.

A general brew-sudo helper

A better cross-platform approach is to define a small helper that asks Homebrew for its prefix, checks both bin and sbin, and then runs the matching executable with sudo.

Now you can run Homebrew-installed commands with root privileges like this:

This works across Linux and macOS because brew --prefix returns the correct Homebrew prefix for the current system.

Defining command-specific wrappers

You can then define convenient wrappers in terms of brew-sudo:

This gives you the best of both worlds:

while internally resolving to something like:

on Linux, or the corresponding Homebrew path on macOS.

Full version

Put this in a shell file sourced by both bash and zsh, or copy it into both ~/.bashrc and ~/.zshrc:

A note on sudo and Homebrew

This approach does NOT run brew itself with sudo. It only uses brew --prefix to locate Homebrew’s installation directory, then runs a specific Homebrew-installed executable with sudo.

That distinction matters: running brew install or brew upgrade with sudo is generally the wrong approach, but running a tool like nethogs with sudo is appropriate when the tool itself needs root privileges.

Alternative: adding Homebrew to /etc/sudoers

Another possible solution is to teach sudo about Homebrew’s executable directories.

On many systems, sudo uses a restricted path configured in /etc/sudoers, often through a setting called secure_path. You can inspect and edit it with:

You may see a line like this:

One could add Homebrew’s bin and sbin directories there. For example, on Linuxbrew:

Or on Apple Silicon macOS:

After that, this may work directly:

However, this approach has a security tradeoff.

It does not give users new sudo permissions by itself. A user still needs to be allowed to run commands with sudo in the first place. But it does make Homebrew-installed commands part of sudo’s command lookup path.

That can be undesirable because Homebrew prefixes are often writable by the normal user, or by users in an admin group. In general, root’s PATH should avoid directories that non-root users can modify. Otherwise, it becomes easier to run a user-controlled executable as root accidentally.

For that reason, I prefer the wrapper approach shown above.

Bootstrapping chezmoi from HTTPS to SSH After First Apply

When I bootstrap a new machine with chezmoi, I usually start by cloning my dotfiles repo from GitHub over HTTPS:

That works well for the very first setup, because my SSH keys are not installed yet.

After the first chezmoi apply, though, my SSH keys are finally in place, and I want the dotfiles repository itself to switch from HTTPS to SSH automatically.

This sounds simple, but there is a small gotcha.

The goal

I wanted chezmoi apply to do this at the end of the first run:

  • detect that the repo remote is using HTTPS
  • convert it to the SSH form
  • update origin automatically

For example:

  • from https://github.com/username/dotfiles.git
  • to git@github.com:username/dotfiles.git

My first attempt

My first version used a run_once_after_... script that called chezmoi source-path to find the source repo:

But when I ran chezmoi apply, I got this:

Why this fails

The problem is that the script is being run by chezmoi apply.

So if the script itself runs another chezmoi command, the second process tries to acquire the same persistent state lock that the original chezmoi apply already holds.

In other words:

  • chezmoi apply starts
  • it runs your script
  • your script runs chezmoi source-path
  • the nested chezmoi process waits on the lock
  • eventually it times out

The fix

The solution is to avoid calling chezmoi inside the script.

Instead, make the script a template and use {{ .chezmoi.sourceDir }} to inject the source directory path when chezmoi renders the script.

Here is the working version.

Working script

Create this file:

Contents:

Why this works

There are two important details here.

1. The script is a template

The .tmpl suffix tells chezmoi to render the file before executing it.

That means this line:

gets replaced with the actual source directory path ahead of time.

No extra chezmoi process is needed.

2. The script runs after “apply”, and only once

Because the file name starts with run_once_after_, chezmoi runs it after the apply phase, and only once per machine unless the script changes.

That makes it a good fit for first-boot setup tasks like this one.

A note about the working directory

One subtle point: chezmoi scripts do not run in the source repo by default.

So even though we know the source directory, we still need to explicitly run:

before calling git remote get-url or git remote set-url.

Resetting the script state

If you need to test the script again after it has already run successfully, you can clear chezmoi’s script state:

That lets run_once_ scripts execute again.

Final thoughts

This ended up being a neat example of a general chezmoi rule:

If a script is already running inside chezmoi apply, avoid launching chezmoi again from that script.

When you need chezmoi-specific information in a script, templating is often the better solution.

For my bootstrap flow, this gives me the best of both worlds:

  1. clone over HTTPS when no SSH key exists yet
  2. install SSH keys during the first apply
  3. automatically switch the dotfiles repo to SSH at the end

Exactly what I wanted.


If you use a similar bootstrap flow, hopefully, this saves you from the persistent state lock timeout.

Fedora Silverblue: upgrading

I’m continuing my evaluation of the Fedora Silverblue Linux distribution, which I started some time ago.

Let’s upgrade the system!

As mentioned in the official documentation:

On Atomic Desktops, you can keep using your computer while applying OS updates as they are downloaded and installed in the background. Once an update has been installed, you can reboot your computer to start using the new version. You will not have to wait for the update to be installed either during shutdown or boot up.

On Fedora Silverblue and Fedora Kinoite, OS updates are downloaded automatically and you will be notified when updates are ready to be applied via a reboot. This behavior can be changed in the settings.

On a fresh installation, these are the details of the installed system:

If we open the Gnome Software, we can see that updates are already detected:

Actually, the system is already downloading the updates (and applying them on a new image); after a while, you can simply restart:

After the reboot, the updated image will take effect:

In particular, just rebooting will lead you to a possibly already downloaded and updated image.

Automatic update behavior can be seen in the Gnome Software preferences:

From the command line, you can check available updated images:

Note the leading “dot” telling you what image you’re currently on.

After reboot, here’s the updated information:

You can also use the GRUB menu to possibly boot into the previous image:

You can then verify that you booted into the previous image:

The “status” command also shows when the new image is being downloaded; for example, after a fresh installation, a new upgrade will be automatically checked and downloaded:

You can also update using the command line. To check for available updates without downloading them, run:

For example, let’s say that I don’t have the latest update (see the status), I check for possible new updates:

I can perform the upgrade with:

And reboot.

With “–preview”, you can also see what’s going to be upgraded, for example:

Stay tuned for more blog posts about Fedora Silverblue!

Running Waybar with a Custom Configuration Directory in Sway

When setting up Sway as your Wayland compositor, you might want to organize your Waybar configuration in a non-standard location — for example, keeping a dedicated ~/.config/waybar/sway/ directory to separate your Sway-specific bar configuration from others.

This seemingly simple task comes with a couple of subtle pitfalls worth knowing about.

The ~ Expansion Problem

The most natural approach would be to launch Waybar directly from the bar block in your Sway config:

Unfortunately, this does not work. The swaybar_command directive does not perform shell expansions, so ~ is passed literally to the command instead of being expanded to your home directory. Waybar will fail to find the configuration file.

A workaround is to launch Waybar via exec instead:

However, this approach has its own drawback: Waybar launched via exec is no longer managed by Sway’s bar subsystem. This means that Sway bar commands — such as swaymsg bar hidden_state toggle to show/hide the bar — will not work.

The Solution: A Wrapper Script

The cleanest solution is to write a small wrapper script that handles the expansion and launches Waybar with the desired configuration:

Make it executable:

Then reference it in your Sway config:

Now Waybar is properly managed by Sway’s bar subsystem, and the script handles path expansion.

The PATH Problem

There is one more subtle issue: the script lives in ~/.local/bin/, which may not be in PATH when Sway starts. Sway is typically launched from a display manager or TTY, where the environment is minimal and does not source your shell’s configuration files (e.g., ~/.bashrc or ~/.zshrc).

To ensure ~/.local/bin is in PATH for Sway and all processes it spawns, add it in your login shell profile:

Your display manager or TTY login will source this file, making the script discoverable when Sway starts.

A first look at Fedora Silverblue 43

I’m going to test the Fedora 43 Linux distribution in its “immutable” “atomic” shape, Silverblue.

Fedora Silverblue is Fedora’s atomic, GNOME-based desktop built around an image-based, mostly read-only system rather than a traditional mutable Linux install.

  • As an atomic desktop: the base OS is image-based, updates are applied as a new deployment, and you switch to it on reboot. That gives you an easy rollback if an update goes wrong.
  • It is designed to separate apps from the host OS: GUI apps are primarily installed as Flatpaks, command-line/dev tooling is commonly run in Toolbx containers, and traditional RPMs can still be added through the rpm-ostree layering when needed.
  • The main upside is stability and reproducibility: Fedora says Atomic Desktops are intended to be more stable, easier to test, and well-suited to containerized apps and development.

Thus, it is rather different from other standard “mutable” Linux distros, especially when it comes to installing software.

I’m starting a series of blog posts on Fedora Silverblue.

At the moment, I’m NOT considering using it as my daily driver (which, for now, is Arch, specifically EndeavourOS).

The installation process

The installation procedure is the typical Fedora one:

It detected my language (though I then switched to English):

Then, I switched to the Italian keyboard layout:

I checked the time, and it was already correct; I also checked the Internet time synchronization:

Time to deal with disk partitioning (“Installation Destination”).

Since I’m testing this on a virtual machine, I’ll stick with the automatic partitioning.

And we can now start with the installation:

The “Writing objects” text stays there for a long time.

Then, the rest of the installation steps are performed, including the installation of apps:

Then, the progress bar basically goes directly to the end. The installation completed in a few minutes:

First boot

During the first boot, you configure a few things (most of them, taken from the installation process, but to be confirmed):

I’ll skip the GNOME tour.

The installed system

Concerning installed applications, it’s rather minimal:

You have a terminal (not the Gnome terminal, but “ptyxis) and Firefox:

Let’s see the layout of the partitions:

So the main partitions are BTRFS.

Note the symlinks (especially for home):

Here’s the Gnome software center:

Of course, since Silverblue is immutable (you can’t modify the root system), applications are installable through flatpaks from the software center:

Concerning the installed apps:

They are also flatpaks.

However, concerning the “System Apps”:

I seem to understand they are “layered” on the base image:

There are already some updates:

We’ll deal with upgrades in a future blog post.

That’s all for the moment, but I’ll keep exploring and experimenting with this distro.

Neovim and LaTeX with LazyVim, part 3: custom commands

This is a follow-up to the first post on Neovim and LaTeX and the second one.

If you write LaTeX in Neovim using the LazyVim distribution, you already get a fantastic editing experience thanks to VimTeX and texlab. But there’s one thing that often trips people up: how do you quickly wrap selected text in \emph{…}, \textbf{…}, or other LaTeX commands?

The answer is mini.surround — and with a small buffer-local configuration, it becomes a LaTeX wrapping powerhouse.


What mini.surround Gives You (Out of the Box)

LazyVim ships with the mini.surround extra, which you can enable in your lazy.lua (or wherever you manage extras):

Once enabled, you get operations like:

Keymap Action
gsa Add surrounding
gsd Delete surrounding
gsr Replace surrounding
gsf Find surrounding

For example, visual-select a word and press gsa" to wrap it in double quotes, or gsa) for parentheses. Great — but what about LaTeX-specific commands?


The Problem with LaTeX Commands

VimTeX gives you text objects, motions, completion, and much more — but it doesn’t ship a built-in “surround this selection with \emph{…}” feature. That’s where mini.surround‘s custom surroundings come in.

We can teach mini.surround to understand LaTeX commands by defining custom surroundings in a filetype-specific Lua file.


Setting Up Custom LaTeX Surroundings

Create (or edit) the file ~/.config/nvim/after/ftplugin/tex.lua. This file is automatically sourced by Neovim whenever you open a .tex file.

Why ftplugin? Files in this directory are loaded after all plugin configuration, and only for the matching filetype. This means our settings are buffer-local and don’t interfere with any other filetype.


How It Works

The cmd_surround helper function does two things:

  1. input: A Lua pattern that matches \cmd{...} so that gsd and gsr know what to find and remove.
  2. output: The left (\cmd{) and right (}) strings used when adding a surrounding.

We then assign short, memorable single-character IDs to each command.


Using the Surroundings

Here’s what you can do in any .tex buffer:

Adding a Surrounding

Visual-select some text, then:

Keymap Result
gsae \emph{selected text}
gsab \textbf{selected text}
gsai \textit{selected text}
gsat \texttt{selected text}
gsau \underline{selected text}
gsas \textsc{selected text}
gsaq (add opening and closing LaTeX quotations, i.e., two backticks and ”)

You can also use gsa in normal mode with a motion: gsaeiw wraps the current word in \emph{…}.

Deleting a Surrounding

Place your cursor inside a command and press:

Keymap Action
gsde Remove \emph{…}, leaving the inner text
gsdb Remove \textbf{…}
gsdi Remove \textit{…}

Replacing a Surrounding

Want to change \emph{…} to \textbf{…}? No problem:

That’s gsr (replace surrounding), then e (find \emph{…}), then b (replace with \textbf{…}).


A Practical Example

Suppose you have this text:

You visual-select “very important” and press gsab. You get:

Now you decide that emphasis is better. With the cursor inside the \textbf{…}, press gsrbe:

And if you want to remove the markup entirely: gsde:


Extending with Your Own Commands

The pattern is easy to extend. Just add more entries to cfg.custom_surroundings:

Pick single characters that are easy to remember and don’t conflict with built-in mini.surround ids (avoid (, ), [, ], {, }, ', ", `).


A Note on Limitations

The input patterns use Lua’s pattern matching, which cannot handle nested braces. For example, \emph{foo {bar} baz} may not be matched correctly by gsd or gsr. For the vast majority of LaTeX writing, this is not an issue — but it’s worth keeping in mind if you work heavily with nested commands.


Happy LaTeX writing! 🎓

Run mako in Sway/Hyprland, but not in KDE (Wayland)

If you use a Wayland compositor like Sway or Hyprland, mako is a great lightweight notification daemon. Many distros make it “start automatically” via a systemd user service, so you don’t need to add exec mako to your compositor config.

I’m experimenting with a setup with KDE, Sway, and Hyprland installed on the same machine and used by the same user, and there’s a catch: KDE on Wayland is also a Wayland session, so the same auto-start logic can kick in there too — and you end up with mako running in Plasma when you don’t want it.

This post shows a clean fix: keep mako auto-starting in Sway/Hyprland, but skip it in KDE, using a systemd drop-in override that lives nicely in your dotfiles (e.g., with chezmoi).


Why mako starts in KDE

On many systems, the provided user unit checks only whether you’re in a Wayland session:

KDE Wayland sets WAYLAND_DISPLAY too, so mako passes the check and runs.


The fix: restrict the systemd unit to Sway/Hyprland

Create a systemd user drop-in:

This opens an editor and writes a file under:

  • ~/.config/systemd/user/mako.service.d/override.conf

Put this in that file:

Apply it:

Now mako should start in Sway/Hyprland, and be skipped in KDE.


Verify what systemd is using

To see the merged unit (vendor file + your override):

To confirm whether it’s running and why:

If you log into KDE and it’s skipped, you’ll typically see a reason related to the ExecCondition.


Making it portable with chezmoi

Since systemctl --user edit produces a file in your home directory, it’s perfect for dotfiles.

With chezmoi, just track:

  • ~/.config/systemd/user/mako.service.d/override.conf

On another machine, once mako is installed, that drop-in will be picked up automatically. The only thing to remember is that systemd reads unit changes after a reload (or next login), so if you want it to take effect immediately after chezmoi apply, run:

(try-restart is nice because it won’t error if mako isn’t running.)


Bonus note: D-Bus activation

Depending on your distro and setup, mako can also be started via D-Bus when a request for notifications is made. If you find mako still appears in KDE even when the unit is skipped, check whether it’s being D-Bus activated — and then either:
– rely on a KDE-native notification daemon in Plasma, or
– mask the D-Bus service for org.freedesktop.Notifications for KDE sessions only.

(Usually, the systemd drop-in above is enough.)

Using KWallet in Sway for Chrome

If you use KDE and Sway (or Hyprland) on the same machine with the same user (something I’m experimenting with), when you launch Chrome and log in with your user in KDE, and then switch to Sway, you’ll see that your account is marked as “Paused”: you have to log in again. The same holds the other way round.

That’s because in KDE, Chrome stores the credentials in KWallet, while Sway does not.

To fix this annoying problem, you have to ensure to run Chrome in Sway with the option “–password-store=kwallet6”.

To do that, you can either manually launch Chrome with that option or create the file “~/.local/share/applications/google-chrome.desktop” starting from the default file (in Arch it’s “/usr/share/applications/google-chrome.desktop”) and ensure the occurrences of the “Exec” line have that option, i.e.,

The desktop file in your home folder will have precedence over the default one.

However, while KDE automatically unlocks the KWallet when you log in, Sway does not. The first time you launch Chrome from a Sway session, you’re asked to open the wallet with your login password:

To let KWallet use your login credentials automatically (as KDE does, thanks to “/etc/xdg/autostart/pam_kwallet_init.desktop”), you need to tell Sway to start the corresponding PAM module.

The ArchWiki recommendation for “a window manager” setup is simply: run pam_kwallet_init from your WM/compositor startup.

Add this to ~/.config/sway/config (early in startup, before apps that need secrets):

That’s usually enough to avoid the post-login password prompt, provided PAM is already set up correctly (see the next section).

Important: pam_kwallet_init only works if PAM captured your password

pam_kwallet_init does not magically know your login password. It relies on the PAM module (pam_kwallet5.so) having captured it during login and made it available for the session handoff.

So:

  • If you start Sway via a display manager/session that already has pam_kwallet5.so in its PAM stack, then running /usr/lib/pam_kwallet_init In Sway, the wallet should unlock automatically.

  • If you start Sway via a path that doesn’t run the kwallet PAM module (common when starting from TTY, or via some greeters, depending on config), then pam_kwallet_init won’t have credentials to use, and you’ll still be prompted.

Installing Linux on a Dell Pro Max Tower T2

I’ll show how I installed Linux (EndeavourOS, i.e., Arch) on a Dell Pro Max Tower T2.

This is quite a powerful computer! Here are a few screenshots taken from Windows 11:

Before installing Linux on this computer, I had to change the SSD SATA mode from RAID to AHCI, as documented in a previous blog post. Otherwise, Linux will not detect any SSD.

Prepare the disk with “Disk Management”.

Current situation:

I will not wipe the whole disk because I want to use Windows as well. I won’t touch the other recovery and health partitions either.

Right-click on “C:” and choose “Shrink Volume…”. About 200Gb should be enough for Windows on this computer. Unfortunately, the UI of this dialog is not the best one: you have to compute how much space to remove from the current partition and check whether the “Total size after shrink” is what you want.

I’m also deleting the “D:” volume (I’ll use it for additional partitions both on Linux and maybe on Windows).

Here’s the final result:

Let’s reboot the computer and turn off secure boot. Press F2 when the computer is turning on to enter the BIOS.

Very nice looking:

NOTE: You can use the mouse to navigate the BIOS. In my case, the computer is connected to a KVM switch for keyboard and mouse. When in the BIOS, the mouse just moves vertically. I had to plug the mouse directly into the USB port of the computer to use it inside the BIOS.

Select “Boot Configuration” where you see the “Boot Sequence” (that’s useful in the future to change the boot order or delete old entries). Scroll down til you get to secure boot and disable it:

Let’s apply changes and exit.

I downloaded the EndeavourOS ISO Mercury Neo 2025.03.19, put it into a Ventoy USB stick.

It looks like this PC can boot from USB only from the first port from the bottom (at least in the front: I haven’t tried the ports on the back):

When the computer starts, press F12 for the temporary boot menu. Select the USB stick. Then you get the Ventoy menu where you select the EndeavourOS distribution. I’ll use grub2 mode.

The installation is the typical one for EndeavourOS, which I have already blogged about.

After several weeks, I can confirm that Linux works like a charm on this powerful computer! 🙂

Install Endeavouros with Disk Encryption

A small blog post on how to install EndeavourOS with LUKS disk encryption.

The installation starts and proceeds as usual (see, e.g., my older post). I’m using KDE for this installation.

When you get to disk partitioning, choose manual:

If you start from a fresh disk, create a new partition table and choose GPT:

If you start from a fresh disk, first create the partition for EFI:

In the rest of free space, I personally prefer to have a swap partition:

But I prefer not to encrypt that (to avoid being asked for the decryption password twice; at least, that’s what I guess… I’ll experiment that in the future):

Then, the partition for the actual system (in this case, I’m not using the whole disk); here, you specify the filesystem (I prefer BTRFS) and check “Encrypt”; you’ll be asked for the encryption password (of course, choose a strong one and ensure you remember that password):

Here’s the final layout:

Now, proceed as usual.

When you reboot, before getting to GRUB, you’ll be asked for the encryption password:

Once inserted, wait for the system to verify that:

And then, you finally get to GRUB as usual.

In fact, I haven’t created a separate partition for “/boot” (which would be a bad idea if you want to use BTRFS snapshots); thus, the grub configuration is in the encrypted file system, and when EFI boots, it needs the encryption password right away.

When you log in, you should see the directory layout with LUKS encryption:

Enjoy your encrypted system! 🙂

Neovim and LaTeX with LazyVim, part 2: customizations

This is a follow-up to the first post on Neovim and LaTeX.

I love LaTeX, but I don’t love LaTeX noise. If you use LazyVim’s lang.tex extra, you’ve probably seen a familiar friend pop up in your editor diagnostics: Underfull \hbox. It’s usually harmless, but it’s distracting—especially when you’re trying to focus on content. In this post, I’ll show two clean ways to silence those warnings: one for VimTeX’s quickfix and one for TexLab’s LSP diagnostics, without turning off the good stuff.

In LazyVim’s Tex extra, those Underfull \hbox … messages are coming from VimTeX’s Quickfix parsing (it’s enabled by default in the extra). VimTeX lets you hide specific warnings by regex-matching them with g:vimtex_quickfix_ignore_filters.

Create a custom plugin specification, e.g., “~/.config/nvim/lua/plugins/extend-vimtex.lua”, with these contents:

Restart Neovim, and the quickfix list will no longer contain those warnings.

However, you’re still seeing “Underfull \hbox” as LSP diagnostics (not quickfix), that’s coming from TexLab (separate configuration):

You need to create another custom plugin specification for customizing the TexLab LSP, e.g., “~/.config/nvim/lua/plugins/extend-lspconfig.lua”:

Restart, and also the editor’s inline warnings of that space will be gone.

If you don’t like that deep specification, in LazyVim, you don’t have to restate the whole opts = { servers = { texlab = { … }}} block. You can patch the existing LSP config in a tiny opts = function(_, opts) ... end snippet (LazyVim shows this pattern for extending defaults, and also documents the opts.setup hook for server setup).

Here’s an alternative configuration:

Though it’s a bit longer.

The final result of this series of tutorials can be found here: https://github.com/LorenzoBettini/lazyvim-tex. Branches will denote the state of the repository for a specific blog post section.

Stay tuned for more blog posts on LaTeX and Neovim! 🙂

Stop VS Code’s Java LSP from Rewriting Your Eclipse .classpath with m2e-apt Entries

How to prevent JDT LS (via m2e) from adding generated-sources APT folders and org.eclipse.jdt.apt prefs to an Eclipse+Maven project in VS Code.

If you open a Maven Java project in Visual Studio Code that also contains Eclipse project metadata (.project, .classpath, .settings/…), you might notice that VS Code’s Java tooling (JDT Language Server) “helpfully” edits your Eclipse files.

In particular, it may keep re-inserting entries like these into your .classpath:

…and it may also create this file:

.settings/org.eclipse.jdt.apt.core.prefs

VS Code Java support is powered by Eclipse JDT Language Server (JDT LS). When it detects Eclipse metadata (.project / .classpath), it will often use Eclipse-style project configuration and keep it synchronized.

For Maven projects, JDT LS relies on m2e (the “Maven integration for Eclipse”), and in many setups m2e-apt is present as well. m2e-apt is the component that manages annotation processing (APT) integration and, as part of that, it adds the standard “generated sources” folders into .classpath.

I find that very annoying!

If your project doesn’t use annotation processing and you don’t want these Eclipse files constantly modified, and you remove the entries, Visual Studio Code will re-add them when you open the projects in Visual Studio Code. If you open the projects from Eclipse and you “update” the Maven projects, Eclipse will remove the entries… and so on and so forth!

Here’s how to fix things for good.

The fix: disable m2e-apt at the project level

m2e-apt supports overriding its activation per-project using a Maven property:

Add this to your pom.xml

Put it under the regular Maven <properties> section:

That’s it. After this, m2e-apt will stop treating your project as something it should manage, and VS Code/JDT LS will no longer keep reintroducing those APT-related .classpath entries.

Note: The documentation mentions a “settings section” in the POM. There is no settings element in pom.xml; Maven “settings” live in ~/.m2/settings.xml. In the POM, this is implemented via a property, so properties (or a profile’s properties) is the right place.

Refresh VS Code so it stops regenerating the files

After editing the POM, VS Code may still have cached the project configuration. Do this once:

  1. Open the Command Palette
  2. Run: Java: Clean Java Language Server Workspace
  3. Let the Java server restart and re-import the project

Then you can delete the unwanted entries/files one last time:

  • Remove the APT-related classpathentry … m2e-apt … blocks from .classpath
  • Delete .settings/org.eclipse.jdt.apt.core.prefs if you don’t want it around

They should not come back.

If you only want to disable it in certain environments, you can place the property in a Maven profile or in the pom file of a single project.

Happy (quiet) classpaths! 😉

LaTeX listings: Eclipse colors

This is the style I use to highlight my Java code in LaTeX documents with the Listings package, with Eclipse colors:

And this is an example of a document, where I show the same listing both with black and white colors an Eclipse colors:

Here’s the result:

Browse and run your Sway keybindings with Rofi

Remembering every Sway shortcut is tough. I wrote a small script that parses your Sway config, displays all bindsym shortcuts in a clean, searchable list via Rofi, and executes the command associated with whichever one you select.

It’s fast, keyboard-friendly, and great for discovery: “What did I bind to Mod + Shift + P again?” Now you can search, see, and execute it.

What the script does

  • Reads your Sway config from $XDG_CONFIG_HOME/sway/config (or ~/.config/sway/config)
  • Finds all bindsym … lines
  • Formats each entry nicely, e.g.

Mod + Return → exec alacritty

  • Shows the list in a wide Rofi dmenu
  • When you select an entry, it executes the associated command through swaymsg

Dependencies

  • sway (for swaymsg)
  • rofi
  • awk, sed, grep (standard on most distros)
  • notify-send (optional – shows an error if the config isn’t found)

The script

Save this as ~/.local/bin/rofi-sway-keybindings.sh and make it executable.

How it works

The core of the script is a small text-processing pipeline that reads the config and renders a nice two-column list for Rofi:

  1. grep -E '^\s*bindsym' finds all bindsym lines (ignoring leading whitespace)
  2. grep -v '^[[:space:]]*#' ignores full-line comments
  3. sed 's/^\s*bindsym\s*//' strips the leading bindsym
  4. awk splits the line into (and does some cleanup):

keys: the first token (e.g. $mod+Return)
cmd: the rest of the line (e.g. exec alacritty)

It also strips trailing inline comments (after #) and skips bindsym flags like --release or --locked before reading the key. Finally, it prettifies modifiers and prints a fixed-width column so the arrows line up.

Rofi presents that list with -dmenu. When you pick one, the script extracts the command part (after ) and sends it to swaymsg. That means anything you can put after bindsym (like exec …, workspace …, kill, etc.) will run on demand.

Usage

  • Run the script from a terminal: rofi-sway-keybindings.sh
  • Or bind it to a key in your Sway config, e.g.:

Tip: the window is set to 60% width for readability; tweak it via -theme-str if you prefer.

Nice touches in the UI

  • Replaces $mod with Mod
  • Shows Shift as and Control as Ctrl
  • Adds spacing around + so chords read clearly: Mod + ⇧ + q
  • Aligns the left column to 35 characters for a tidy two-column look

Why this is handy

  • Onboarding: perfect for new setups or when you come back after a while
  • Discovery: search by key or by command to find what you already have
  • Launcher: use it as a programmable “cheat sheet” that also runs things

Here are some screenshots (including filtering):

Modern Java in LaTeX listings (Java 17)

If you use the LaTeX listings package to typeset Java, you’ve probably noticed that modern Java has moved faster than the package itself. Records, var, and text blocks may not highlight correctly out of the box. The good news: the listings package is extensible so that you can teach it “modern Java” with a tiny language definition.

The minimal language extension for Java 17

Here’s a drop‑in snippet that builds on the stock Java lexer to support key Java 17 features:

What each line does:

  • language = Java: inherit all of the listings’ built‑in Java rules.
  • morekeywords = {var,record}: colorize var and record as keywords (var is contextual, but highlighting it improves readability in code listings).
  • deletekeywords = {label}: avoid mistakenly highlighting labeled statements like label: for (…) { … }. label is not a Java keyword; removing it prevents false positives.
  • morestring=[b]”””: treat triple quotes as a balanced string delimiter so Java text blocks highlight as a single string.

Using it in your document

Activate the language globally:

…or per listing:

If you already have a custom style (e.g., mystyle) with colors and fonts, combine them:

Minimal working example

This is a compact MWE you can compile with pdflatex, xelatex, or lualatex. Adjust the style to your taste:

Here’s the result, where you can see the differences (note in the standard behavior the wrong highlighting of the double-quoted string in the text-block):

Happy highlighting! 🙂

Getting Your MacBook Air Webcam Working on Linux

If you’ve installed Linux on your MacBook Air, you’ve probably discovered that while most hardware works out of the box, the built-in FaceTime HD camera is notably absent from your video applications. Don’t worry—you’re not alone, and there’s a solution that doesn’t involve external USB webcams or complicated workarounds.

The issue stems from Apple’s use of Broadcom’s proprietary FaceTime HD camera hardware. Unlike most standard USB webcams that work with Linux’s UVC (USB Video Class) drivers, the MacBook Air’s camera uses a PCIe interface with the Broadcom 1570 chipset, which requires specialized drivers that aren’t included in the Linux kernel.

Identifying Your Hardware

Before diving into the solution, let’s confirm you have the same hardware. Open a terminal and run:

If you see output similar to this, you’re dealing with the same Broadcom camera:

The key identifier here is [14e4:1570]—this tells us we have the Broadcom 1570 chipset that needs the reverse-engineered driver.

The Solution: Community-Developed Drivers

Thanks to the hard work of the Linux community, reverse-engineered drivers are available through the Arch User Repository (AUR). These drivers have been developed by analyzing the hardware behavior and creating open-source alternatives to Apple’s proprietary drivers.

If you’re using an Arch-based distribution (like EndeavourOS, Manjaro, or plain Arch), you can search for the available packages:

This will show you several packages:

  • facetimehd-dkms: The main reverse-engineered driver
  • facetimehd-firmware: Required firmware files extracted from macOS
  • facetimehd-data: Sensor calibration data for optimal performance
  • facetimehd-dkms-git: Development version of the driver

The installation is straightforward. Install the main driver package:

The package manager will automatically pull in the required dependencies, including the firmware and calibration data packages.

After installation, you must reboot your system. This isn’t just a suggestion—the kernel module needs to be loaded fresh, and the hardware needs to be properly initialized during the boot process.

Once your system boots back up, your webcam should be functional. However, there’s an important caveat to be aware of.

While the driver successfully enables the webcam, there are some compatibility quirks:

  • Google Chrome/Chromium: Works perfectly
  • Firefox: May not detect the camera
  • Native Linux applications (like Kamoso): May have issues

This inconsistency likely stems from the different ways in which various applications interact with the video4linux (V4L2) subsystem and handle the specific quirks of this reverse-engineered driver.

Maintaining KDE dotfiles with Chezmoi Modify Manager

I have already blogged about managing KDE dotfiles with chezmoi and chezmoi_modify_manager. But what about maintaining them?

For example, one of the KDE configuration files changes, and you want to update the version managed by chezmoi.

Here’s an example where the Kate configuration file changed on the system and chezmoi detects that:

You can see the change with “chezmoi diff”:

Remember that the part with “+” is the version known by chezmoi, while the one with “-” is the version in the local system.

You now want to update the corresponding file managed by chezmoi.

The command “chezmoi re-add” won’t help because that file is handled by chezmoi_modify_manager, which splits it into two files: “modify_private_katerc” and “private_katerc.src.ini”.
The latter contains only the parts of the file we want to track, and the former is the corresponding modification script.

Moreover, “chezmoi merge” won’t help either for the same reason. Here’s what this command shows (I configured chezmoi to use the GUI program “meld” for such a command):

However, “chezmoi_modify_manager” has an option for such situations. Here’s the option to use with “chezmoi_modify_manager”:

Here’s the command and the output:

By using “chezmoi cd” and “git diff”, we can verify that the corresponding “.src.ini” file has been correctly updated (and the git repository can then be committed and pushed):

In the end, it’s easy, once you know the right option to use with “chezmoi_modify_manager”!

Enjoy your KDE dotfiles! 🙂

Configure Tmux to support true color and italics in Alacritty and Neovim

I know there are many blog posts about configuring Tmux to support true color and italics in Alacritty, but many of them miss a crucial detail that breaks Neovim’s diagnostic undercurl (wavy underlines).
Many of them suggest overriding the TERM variable in Alacritty to xterm-256color, which causes Neovim to lose the ability to display undercurl correctly.
Many of them are also outdated or incomplete.

This is how I configured Tmux and Alacritty to work perfectly together, supporting true color, italics, and Neovim’s undercurl diagnostics.

The Problem

When using Tmux inside Alacritty, you may encounter several issues:

  1. No True Color Support: Colors may appear washed out or limited to 256 colors instead of the full 24-bit RGB spectrum
  2. Italics Not Working: Italic fonts don’t render correctly, or reverse video appears instead
  3. Neovim Diagnostic Undercurl: Neovim’s diagnostic undercurl (wavy underlines) shows as simple underlines instead

These issues stem from incorrect TERM environment variable configuration and missing terminal capability overrides.

For example, using the shell script linked below to check true color support, when it’s not working, you get (left: Tmux inside Alacritty, right: Alacritty by itself):

When it works, you get:

The Solution

1. Alacritty Configuration

DO NOT override the TERM variable in Alacritty. Leave it at the default value alacritty.

Why? Alacritty’s default TERM=alacritty includes the terminfo capabilities for undercurl, which Neovim needs to display diagnostic wavy underlines. Setting it to xterm-256color breaks this feature.

2. Tmux Configuration

Add the following lines to your ~/.tmux.conf:

Explanation:
default-terminal "tmux-256color": Sets tmux to use a terminal type that supports 256 colors and italics
terminal-overrides ",*:Tc": Tells tmux to enable true-color (24-bit RGB) support for all terminal types that support it

3. Why This Works

When you start tmux inside Alacritty:
– Alacritty sets TERM=alacritty (which supports true color and undercurl)
– Tmux creates a new environment with TERM=tmux-256color (which supports italics)
– The terminal-overrides setting tells tmux to pass through true-color escape sequences from the outer terminal (Alacritty)

This combination gives you:
– True color (24-bit RGB) support
– Italic fonts working correctly
– Neovim undercurl diagnostics rendering properly

Verification

To verify everything works:

Check true color support:

Check italics:

Check in Neovim:

  • Open a file with syntax errors or any diagnostics (including spelling errors)
  • You should see wavy underlines (undercurl) for diagnostics, not simple underlines

References

How we used Maven relocation for Xtend

In Xtext release 2.40.0, we adopted Maven relocation to move Xtend Maven artifacts’ groupIds from org.eclipse.xtend to org.eclipse.xtext without breaking existing consumers.

References:

Rationale

Xtend’s Maven coordinates were relocated to comply with Maven Central’s new publishing requirements after the OSSRH sunset.

The new Maven Central publishing portal enforces namespace consistency: all artifacts in a single deployment must share the same groupId prefix (namespace). We were getting this error when trying to deploy:

Xtend Maven artifacts historically had groupId org.eclipse.xtend, while all other Xtext Maven artifacts use org.eclipse.xtext. This mismatch prevented us from publishing both in a single deployment to Maven Central.

See the detailed rationale in issue #3398 and specifically this comment.

Why relocation instead of just renaming

  • Backwards compatibility: Existing builds continue to resolve, emitting a clear warning rather than failing.
  • Gradual migration: Library and plugin maintainers can update on their own schedule.
  • Single source of truth: Only the new artifact publishes real content; the old coordinate becomes a lightweight stub POM.
  • Clear deprecation signal: A relocation message is more explicit than a silent artifact disappearance.
  • No breaking changes: Consumers don’t need to update immediately; their builds keep working.

Maven relocation basics (summary)

Maven relocation allows you to redirect artifact coordinates without breaking existing consumers.

The process involves:

  1. Real artifacts with the new groupId that contain actual JARs, source, and javadoc
  2. Relocation artifacts with the old groupId that are minimal POMs pointing to the new coordinates

A relocation artifact is a simple POM project with this structure:

At resolution time, Maven automatically replaces the old coordinates with the new ones and displays a warning to the user.

Our goals

  1. Preserve build stability for all existing Xtend consumers
  2. Minimize maintenance by publishing only one real artifact per logical module
  3. Provide a clear migration path with visible warnings
  4. Avoid transitive duplication (both old + new coordinates ending up on classpath)
  5. Comply with Maven Central’s namespace requirements

Implementation outline

Our implementation involved several steps (see PR #3461 for details):

Identify artifacts to relocate: All Xtend artifacts published to Maven Central under org.eclipse.xtend: org.eclipse.xtend.lib, org.eclipse.xtend.lib.gwt,org.eclipse.xtend.lib.macro, etc.

Create relocation parent POM: Created org.eclipse.xtend.relocated.parent to organize all relocation artifacts.

For each artifact, create a relocation POM module with:
– Packaging: pom
– Old groupId: org.eclipse.xtend
– Same artifactId and version as the original
– Relocation block pointing to org.eclipse.xtext

Separate publishing workflow: Since Maven Central requires same-namespace deployments, we had to:
– Build relocation artifacts separately
– Archive deployment bundles for manual upload
– Publish relocation artifacts in a separate step from main artifacts

Update CI/CD scripts: Modified Jenkins deployment scripts to handle both artifact sets.

Example relocation POM

Here’s a real example from our implementation:

When a consumer uses the old coordinates, Maven shows:

Ensuring no duplicate classes

Because the relocation artifact is only a stub POM with no JAR attached, the classpath will contain only the new artifact. This prevents:

  • Duplicate classes on the classpath
  • Class shadowing issues
  • Version conflicts between old and new coordinates

Maven and Gradle both handle this correctly by fetching only the relocated target.

Publishing workflow

The key challenge was Maven Central’s namespace requirement. Our solution:

  1. Main build: Publishes all org.eclipse.xtext artifacts (including the real Xtend artifacts with new groupId)
  2. Separate relocation build: Publishes all org.eclipse.xtend relocation POMs
  3. Validation: We performed dry-run deployments to verify Maven Central would accept the artifacts
  4. Manual upload: For milestone releases, we archived bundles and manually uploaded them to Maven Central

As noted in the PR discussion, we had to update version-bumping scripts to include the new relocation parent directory.

Migration guidance for consumers

Search your builds for the old groupId:
– Maven: mvn dependency:tree | grep org.eclipse.xtend
– Gradle: ./gradlew dependencies --configuration compileClasspath | grep org.eclipse.xtend

Replace org.eclipse.xtend with org.eclipse.xtext in your POMs or build.gradle files:

Run your build and verify the relocation warning disappears

Update any BOM or dependencyManagement entries

Tooling considerations

  • IDEs: Eclipse, IntelliJ IDEA, and other IDEs honor Maven relocation. Refresh your project after migration.
  • Gradle: Fully supports Maven relocation when resolving from Maven repositories.
  • Reproducibility: The relocation POMs are stable and don’t affect build reproducibility.
  • CI/CD: No changes needed; relocation works transparently in CI environments.

Lessons learned

  1. Maven Central namespace enforcement is strict: You cannot publish artifacts with different groupId namespaces in a single deployment, even if they’re in the same monorepo.

  2. Relocation is low-effort and highly effective: Once set up, relocation artifacts are trivial to maintain across versions.

  3. Separate publishing is required: Relocation artifacts must be published in a completely separate Maven deployment due to namespace restrictions.

  4. Testing is crucial: We performed dry-run deployments first to ensure Maven Central would validate the artifacts correctly.

  5. Scripts need updates: Don’t forget to update version-bumping and release automation scripts to include relocation modules.

  6. Communication is important: Clear documentation and release notes help consumers understand and adopt the changes smoothly.

  7. It works across ecosystems: Both Maven and Gradle consumers benefit from relocation automatically, as do IDE integrations.

FAQ

Q: Do I need to change anything immediately?
A: No; builds continue to work with the old coordinates, but you’ll see warnings. Update when convenient to eliminate warnings.

Q: Does relocation affect checksums or reproducible builds?
A: No; the new artifact is authoritative. The stub POM exists only for resolution redirection and contains no actual code.

Q: Can Gradle consumers rely on this?
A: Yes; Gradle honors Maven relocation information when resolving from Maven repositories.

Q: What about IDEs?
A: IDEs (tested with Eclipse and IntelliJ) honor Maven relocation when resolving from Maven repositories. You may need to refresh your project after migration.

Q: What if I use dependencyManagement or BOM entries?
A: Update them to reference the new groupId. Transitive relocation continues working in the meantime.

Q: Will this affect my transitive dependencies?
A: No; if your direct dependencies haven’t migrated yet, their use of old coordinates will be automatically relocated, and you’ll see warnings for those too.

Q: What happens if I have both old and new coordinates in my dependency tree?
A: Maven/Gradle will resolve both to the same artifact (the new one), so you won’t have duplicates on the classpath.

Install Ubuntu on Apple Silicon Macs Using UTM

Let’s install Ubuntu 25.04 on Apple Silicon (M1) in a virtual machine using UTM.

We must first download an Ubuntu ISO for ARM. Go to https://ubuntu.com/download/desktop and search for “ARM 64-bit architecture”; if not available for the specific version you want to install, then you’ll have to install the Ubuntu Server and then install the “ubuntu-desktop” package. Before downloading the server edition, you might want to search deeper in the Ubuntu release download site and look for the ARM ISO, which is not advertised in the above download site. For example, for 25.04, you get the link for the ARM version from the download site, but not for 24.04. However, by looking at the releases web directory, you’ll also find the ARM ISO for 24.04 (for example, https://cdimage.ubuntu.com/releases/24.04.3/release/ubuntu-24.04.3-desktop-arm64.iso).

You can install UTM in several ways. I installed with Homebrew: “brew install utm”.

Let’s start it and create a new Virtual Machine:

Choose Virtualize (we want to use an ARM Linux distribution):

Then, of course, choose “Linux”

Here we use QEMU, not Apple Virtualization. We select the ISO we downloaded before:

Let’s change Memory and Cores to allocate to the VM (I also enable hardware acceleration to have nice visual effects in the VM; As specified in the checkbox’s documentation, this might give you troubles in some configurations):

And the storage of the VM disk. Remember that by default, not all the specified GiB will be used by the file of the disk image: the disk image file will occupy only the effectively used data by the filesystem.

For the moment, I won’t use a shared directory.

Here’s the summary, where you can also give a custom name to the VM:

OK, let’s start the VM; the first time, this will start the installation ISO:

Here we are in the VM. From now on, the installation procedure is basically the standard Ubuntu one. Here’s the list of screenshots:

Since that’s a VM, I select to erase the entire (virtual) disk; alternatively, you might want to specify your custom partitioning scheme, possibly with a different file system than the default one, i.e., EXT4.

The installation starts with the usual slide show:

Remember that by clicking on the small icon on the bottom-right, you’ll get the log of the installation:

After installation has finished, shut down the machine (instead of “restart now”) and “Clear” CD to avoid booting from the installation ISO again:

Now, start the installed VM.

As usual, you’ll almost immediately get updates:

If not already installed, you might want to install spice-vdagent and spice-webdavd for better integration with the host system (for example, shared clipboard and folder sharing).

Let’s see a few screenshots of the installed system:

Note the SWAP file created by the installer and the filesystem layout.

I’ve installed fastfetch to show the typical output:

Note the graphics (remember we selected above the Hardware OpenGL Acceleration):

Concerning the resolution of the VM, let’s consider the “Display” setting of the virtual machine:

Note the selected “Resize display to window size automatically”; that’s useful, especially when setting the VM window to full screen.

Concerning the display settings: “Retina mode” (optimize resolution for HDPI displays). Then you have to adjust the resolution in the VM.
The documentation https://docs.getutm.app/settings-qemu/devices/display/ suggests NOT to enable “Retina mode” because it increases memory usage and processing (and the host operating system can use efficient hardware scaling, while the guest uses software scaling).

Without this setting, the display will be presented at the current resolution and scale that the operating system uses. For example, here’s my macOS setting:

And in fact, as you see from one of the screenshots above, the Ubuntu desktop, when the UTM VM is full-screen, has the same 1280×800 resolution.

You might want to have a look at the special input settings:

The “Command+Option” is important to easily switch between the VM and the host OS concerning keyboard inputs.

First impressions

In general, the VM usage is very pleasant. Everything runs smoothly, including the visual effect. Indeed, it all seems to run at native speed.

WARNING: You’re running an ARM Linux distribution, so packages must be available for this architecture. Sad news: a few programs are NOT available for Linux ARM, notably Google Chrome and Dropbox. Please, consider that.

That’s all for this initial UTM post.

Stay tuned for other posts related to Linux virtual machines in UTM.