Skip to content

Monday, 23 February 2026

While working on the Mankala Next Gen project for Season of KDE, I needed to create two distinct logos: one for the game itself and another for the mankalaengine backend. What started as a straightforward design task in Inkscape quickly became a lesson in SVG optimization when my initial exports bal…

Sunday, 22 February 2026

Overview

The KMyMoney 5.2.2 release contains numerous bug fixes and improvements to enhance stability, usability, and performance of KMyMoney. The focus has been on addressing crashes, improving the user interface, and fixing data handling issues. The source code is available on various mirrors world-wide.

Major Changes and Improvements

Stability Improvements

  • Crash Fixes: Multiple crash scenarios have been resolved:
    • Fixed crash when closing split view (Bug 514619)
    • Fixed crash when closing ledger
    • Fixed crash when double clicking schedule group header
    • Fixed crash when applying unassigned difference to split (Bug 515690)
    • Fixed crash in date entry when data has more than 3 sections (Bug 509701)
    • Prevented crash by eliminating lambda slot issues (Bug 510209)
    • Prevented infinite recursion in amount edit widget (Bug 513883)

User Interface Enhancements

  • Keyboard Navigation:
    • Fixed numeric keypad handling when NUMLOCK is off (Bug 507993)
    • Fixed handling of numeric keypad decimal separator
    • Allow Shift+Return to store a split or transaction
    • Allow the equal key to increment the date like the plus key (Bug 507964)
    • Start editing transactions only with specific keys
  • Editor Improvements:
    • Fixed access to tab order editor inside the schedule editor
    • Fixed access to tab order editor inside the split editor
    • Select payee from completer popup with return key (Bug 516300)
    • Focus out on payee widget takes the selected payee from the popup (Bug 508989)
    • Use modified values when pressing Return key (Bug 510217)
  • Visual Improvements:
    • Improved repainting during reconciliation (Bug 514417)
    • Reduced ‘jumping’ of ledger view
    • Changed method to paint selected ledger items
    • Use different background color for transaction and split editor
    • Improved column selector
    • Use full view size if account view has only one column (Bug 511890)

Reports and Charts

  • Report Fixes:
    • Fixed bug in reports that date column is shown too small (Bug 507843)
    • Fixed pivot reports with transactions in closed accounts (Bug 511553)
    • Fixed display of reconciliation report (Bug 507993)
    • Fixed handling of data range (Bug 512718)
    • Fix handling of tags in reports (Bug 511104)
  • Chart Improvements:
    • Include limit lines in balance chart (Bug 513754)
    • Fixed marker line for credit limits (Bug 513187)
    • Show balance and value based on current date

Data Management

  • Transaction Handling:
    • Don’t clear payee of second split onwards in scheduled tx (Bug 511821)
    • Allow changing the memo in multiple transactions at once (Bug 513948)
    • Select new investment transaction created by duplication (Bug 513882)
    • Fixed generation of payment dates in MyMoneySchedule (Bug 513834)
    • Assign an initial payee to each split
    • Partial match on payee name in credit transfer
    • Fixed removal of splits (Bug 508957)
    • Clear the split model before loading another transaction (Bug 509138)
  • Search and Filter:
    • When searching text in the ledger view also consider split memos (Bug 507851)
    • Also search for transaction ID and date in journal filter (Bug 512748)
    • Fixed option to hide unused categories (Bug 514445)
    • Hide zero accounts in payment account section of home page

Investment Features

  • Investment Transaction Editor:
    • Fixed price display in investment transaction editor (Bug 507664)
    • Update label when security changes (Bug 507664)
    • Don’t modify price widget content if not needed (Bug 509454)
    • Provide access to zero balance investments even if filtered out

Budget and Schedule Management

  • Budget Improvements:
    • Allow to modify the budget year when the first fiscal month is january (Bug 515391)
    • Set the dirty flag when changing budget name and year (Bug 514221)
    • Improved setting of the first day in the fiscal year
  • Schedule Features:
    • Allow modifying loan schedule using schedule editor (Bug 509029)
    • Keep the schedule type for loans (Bug 513387)
    • Fixed calculation of number of remaining payments (Bug 509417)

Categories and Accounts

  • Allow direct creation of sub-categories (Bug 514987)
  • Setup sorting of securities to be locale aware (Bug 508529)
  • Keep column settings in institutions view
  • Fixed auto increment of check number

Currency and Localization

  • The Chilean peso has no fractional part (Bug 286640)
  • Support date formats using multiple delimiter characters (Bug 510484)
  • Fixed various i18n calls and translation issues
  • Replaced unsupported i18n.arg in multiple locations

Online Banking

  • Make sure KBanking configuration is stored permanently
  • Prevent duplicate generation of KBanking settings code
  • Prevent editing online job when no id is present (Bug 512665)
  • Show import stats also for Web-Connect imports

Performance Improvements

  • Improved file load time
  • Improved reconciliation performance
  • Don’t update the home page too often
  • Prevent starting the transaction editor during filtering (Bug 508288)
  • Keep selected ledger items only upon first call (Bug 508980)

Technical Improvements

  • Data Integrity:
    • Mark file as dirty after modifying user data (Bug 514575)
    • Report references of splits to unknown accounts and stop loading
    • Use correct method to determine top level parent id (Bug 507416)
    • Speedup check if account id references a top-level account group
  • Build and Dependencies:
    • Fixed build error with Qt 6.10
    • Support Qt < 6.8 (Bug 507927)
    • Add missing find_package for QtSqlPrivate (Bug 509512)
    • Port libical v4 for function name changes
    • Port the icalendar plugin to the upcoming libical version 4
    • Update flatpak runtime to 6.10
    • Various flatpak dependency updates (aqbanking, gwenhywfar, xmlsec, libchipcard)
  • Code Quality:
    • Prevented variable shadowing
    • Resolved compiler warnings
    • Fixed various typos
    • Removed unused code and header files
    • Fixed coverity issues (CID 488310, CID 488368)

VAT Transactions

  • Fixed Net→Gross UI update in new transaction editor for VAT transactions (Bug 514180)
  • Don’t clear/replace debit/deposit boxes during autofill

Home Page

  • Don’t modify running balance when hiding reconciled transactions globally (Bug 508033)
  • Fixed display of preferred accounts on home page
  • Keep current settings of column selector

Miscellaneous

  • Don’t provide defaults that cannot be changed through GUI (Bug 514307)
  • Enable QAction before using it (Bug 508081)
  • Add option to make ledger filter widget visible permanently
  • Use unique style for wizards
  • Move the account selector of the ledger view to the left side
  • Improved onlinejoboutbox view
  • Don’t add the commit hash to tagged versions
  • Start transaction editor if action is triggered (Bug 508420)
  • Emit signal when returning value from calculator in amount widget (Bug 509135)
  • Suppress warning about invalid date in KDateComboBox (Bug 509312)

Bug Fixes by Category

Critical Bugs Fixed

  • Bug 507416 – Crash when opening existing database (REOPENED)
  • Bug 507843 – Link not working on any report
  • Bug 507664 – ‘Total for all shares’ and ‘fraction’ settings ignored when entering investment buy
  • Bug 507851 – Text search doesn’t search through transaction’s memo field if written in a single split item

Additional Bug References

Over 50 bugs were addressed in this release. For a complete list, please refer to the KDE Bugzilla.

Installation and Upgrade

Upgrading from 5.2.1

This release is a drop-in replacement for 5.2.1. Simply install the new version and your existing data files will work without modification.

Flatpak Users

The flatpak version has been updated with the latest dependencies and includes home filesystem access permission.

Known Issues

  • Bug 507416 (crash when opening existing database) has been reopened and is still under investigation for certain edge cases involving legacy data from Skrooge imports.

Contributors

We would like to thank all contributors who helped make this release possible through code contributions, bug reports, translations, and testing.

Getting Help

License

KMyMoney is released under various open source licenses. See the LICENSES folder in the source distribution for details.

Details

A complete description of all changes can be found in the ChangeLog

As part of my work on the Mancala for Season of KDE, I needed to compile the backend mankalaengine from source. Running Ubuntu 24.04 LTS on my host system, I wanted to keep it clean and avoid dependency conflicts while designing digital assets. I decided to build the C++ engine inside an isolated co…
As part of my Season of KDE work on the Mankala game engine, I am trying to impleemnt root level parallelization to speed up the AI's move evaluation. Here's how I achieved about 2x speedup. The Problem: Sequential Move Evaluation The Mankala AI uses the minimax algorithm to evaluate moves, searchi…

Friday, 20 February 2026

To understand the code, you have to understand these four pillars: QXmppOmemoManager: This is the “Encryption Engine.” It handles the Double Ratchet algorithm, which constantly changes the encryption keys for every message so that even if one key is stolen, the rest of the conversation stays safe.

Today we're releasing the second beta of Krita 5.3.0 and Krita 6.0.0. Our thanks to all the people who have tested the first beta. We received 49 bug reports in total, of which we managed to resolve 14 for this release.

Note that 6.0.0-beta2 has more issues, especially on Linux and Wayland, than 5.3.0-beta2. If you want to combine beta testing with actual productive work, it's best to test 5.3.0-beta2, since 5.3.0 will remain the recommended version of Krita for now.

This release also has the new splash screen by Tyson Tan - "Kiki Paints Over the Waves"!

Kiki Paints Over the Waves

To learn about everything that has changed, check the release notes!

5.3.0-beta2 Download

Windows

If you're using the portable zip files, just open the zip file in Explorer and drag the folder somewhere convenient, then double-click on the Krita icon in the folder. This will not impact an installed version of Krita, though it will share your settings and custom resources with your regular installed version of Krita. For reporting crashes, also get the debug symbols folder.

ⓘ Note

We are no longer making 32-bit Windows builds.

Linux

Note: starting with recent releases, the minimum supported distro versions may change.

⚠️ Warning

Starting with recent AppImage runtime updates, some AppImageLauncher versions may be incompatible. See AppImage runtime docs for troubleshooting.

MacOS

Note: minimum supported MacOS may change between releases.

Android

Krita on Android is still beta; tablets only.

Source code

You can build Krita 5.3 using the Krita 6.0.0.source archives. The difference is which version of Qt you build against.

md5sum

For all downloads, visit https://download.kde.org/unstable/krita/5.3.0-beta2/ and click on "Details" to get the hashes.

6.0.0-beta2 Download

Windows

If you're using the portable zip files, just open the zip file in Explorer and drag the folder somewhere convenient, then double-click on the Krita icon in the folder. This will not impact an installed version of Krita, though it will share your settings and custom resources with your regular installed version of Krita. For reporting crashes, also get the debug symbols folder.

ⓘ Note

We are no longer making 32-bit Windows builds.

Linux

Note: starting with recent releases, the minimum supported distro versions may change.

⚠️ Warning

Starting with recent AppImage runtime updates, some AppImageLauncher versions may be incompatible. See AppImage runtime docs for troubleshooting.

MacOS

Note: minimum supported MacOS may change between releases.

Android

Due to issues with Qt6 and Android, we cannot make APK builds for Android of Krita 6.0.0-beta2.

Source code

md5sum

For all downloads, visit https://download.kde.org/unstable/krita/6.0.0-beta2/ and click on "Details" to get the hashes.

Key

The Linux AppImage and the source tarballs are signed. You can retrieve the public key here. The signatures are here (filenames ending in .sig).

Thursday, 19 February 2026

Automating Repetitive GUI Interactions in Embedded Development with Spix

2023-07-05-18-08-38-small_Blog_Christoph_Spix

As Embedded Software Developers, we all know the pain: you make a code change, rebuild your project, restart the application - and then spend precious seconds repeating the same five clicks just to reach the screen you want to test. Add a login dialog on top of it, and suddenly those seconds turn into minutes. Multiply that by a hundred iterations per day, and it’s clear: this workflow is frustrating, error-prone, and a waste of valuable development time.

In this article, we’ll look at how to automate these repetitive steps using Spix, an open-source tool for GUI automation in Qt/QML applications. We’ll cover setup, usage scenarios, and how Spix can be integrated into your workflow to save hours of clicking, typing, and waiting.

The Problem: Click Fatigue in GUI Testing

Imagine this:

  • You start your application.
  • The login screen appears.
  • You enter your username and password.
  • You click "Login".
  • Only then do you finally reach the UI where you can verify whether your code changes worked.

This is fine the first few times - but if you’re doing it 100+ times a day, it becomes a serious bottleneck. While features like hot reload can help in some cases, they aren’t always applicable - especially when structural changes are involved or when you must work with "real" production data.

So, what’s the alternative?

The Solution: Automating GUI Input with Spix

Spix allows you to control your Qt/QML applications programmatically. Using scripts (typically Python), you can automatically:

  • Insert text into input fields
  • Click buttons
  • Wait for UI elements to appear
  • Take and compare screenshots

This means you can automate login steps, set up UI states consistently, and even extend your CI pipeline with visual testing. Unlike manual hot reload tweaks or hardcoding start screens, Spix provides an external, scriptable solution without altering your application logic.

Setting up Spix in Your Project

Getting Spix integrated requires a few straightforward steps:

1. Add Spix as a dependency

  • Typically done via a Git submodule into your project’s third-party folder.
git submodule add 3rdparty/spix git@github.com:faaxm/spix.git

2. Register Spix in CMake

  • Update your CMakeLists.txt with a find_package(Spix REQUIRED) call.
  • Because of CMake quirks, you may also need to manually specify the path to Spix’s CMake modules.
LIST(APPEND CMAKE_MODULE_PATH /home/christoph/KDAB/spix/cmake/modules)
find_package(Spix REQUIRED)
  • Add Spix to your target_link_libraries call.
target_link_libraries(myApp
  PRIVATE Qt6::Core
          Qt6::Quick 
          Qt6::SerialPort 
          Spix::Spix
)

4. Initialize Spix in your application

  • Include Spix headers in main.cpp.
  • Add some lines of boilerplate code:
    • Include the 2 Spix Headers (AnyRPCServer for Communication and QtQmlBot)
    • Start the Spix RPC server.
    • Create a Spix::QtQmlBot.
    • Run the test server on a specified port (e.g. 9000).
#include <Spix/AnyRpcServer.h>
#include <Spix/QtQmlBot.h>
[...]

//Start the actual Runner/Server
spix::AnyRpcServer server;
auto bot = new spix::QtQmlBot();
bot->runTestServer(server);

At this point, your application is "Spix-enabled". You can verify this by checking for the open port (e.g. localhost:9000).

Spix can be a Security Risk: Make sure to not expose Spix in any production environment, maybe only enable it for your Debug-builds.

Where Spix Shines

Once the setup is done, Spix can be used to automate repetitive tasks. Let’s look at two particularly useful examples:

1. Automating Logins with a Python Script

Instead of typing your credentials and clicking "Login" manually, you can write a simple Python script that:

  • Connects to the Spix server on localhost:9000
  • Inputs text into the userField and passwordField
  • Clicks the "Login" button (Items marked with "Quotes" are literal That-Specific-Text-Identifiers for Spix)
import xmlrpc.client

session = xmlrpc.client.ServerProxy('http://localhost:9000')

session.inputText('mainWindow/userField', 'christoph')
session.inputText('mainWindow/passwordField', 'secret') 
session.mouseClick('mainWindow/"Login"')

When executed, this script takes care of the entire login flow - no typing, no clicking, no wasted time. Better yet, you can check the script into your repository, so your whole team can reuse it.

For Development, Integration in Qt-Creator can be achieved with a Custom startup executable, that also starts this python script.

In a CI environment, this approach is particularly powerful, since you can ensure every test run starts from a clean state without relying on manual navigation.

2. Screenshot Comparison

Beyond input automation, Spix also supports taking screenshots. Combined with Python libraries like OpenCV or scikit-image, this opens up interesting possibilities for testing.

Example 1: Full-screen comparison

Take a screenshot of the main window and store it first:

import xmlrpc.client

session = xmlrpc.client.ServerProxy('http://localhost:9000')

[...]
session.takeScreenshot('mainWindow', '/tmp/screenshot.png')k

Now we can compare it with a reference image:

from skimage import io
from skimage.metrics import structural_similarity as ssim

screenshot1 = io.imread('/tmp/reference.png', as_gray=True)
screenshot2 = io.imread('/tmp/screenshot.png', as_gray=True)

ssim_index = ssim(screenshot1, screenshot2, data_range=screenshot1.max() - screenshot1.min())

threshold = 0.95

if ssim_index == 1.0: 
    print("The screenshots are a perfect match")
elif ssim_index >= threshold:
    print("The screenshots are similar, similarity: " + str(ssim_index * 100) + "%")
else:
    print("The screenshots are not similar at all, similarity: " + str(ssim_index * 100) + "%")

This is useful for catching unexpected regressions in visual layout.

Example 2: Finding differences in the same UI

Use OpenCV to highlight pixel-level differences between two screenshots—for instance, missing or misaligned elements:

import cv2

image1 = cv2.imread('/tmp/reference.png')
image2 = cv2.imread('/tmp/screenshot.png')

diff = cv2.absdiff(image1, image2)

# Convert the difference image to grayscale
gray = cv2.cvtColor(diff, cv2.COLOR_BGR2GRAY)

# Threshold the grayscale image to get a binary image
_, thresh = cv2.threshold(gray, 30, 255, cv2.THRESH_BINARY)

contours, _ = cv2.findContours(thresh, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
cv2.drawContours(image1, contours, -1, (0, 0, 255), 2)

cv2.imshow('Difference Image', image1)
cv2.waitKey(0)

This form of visual regression testing can be integrated into your CI system. If the UI changes unintentionally, Spix can detect it and trigger an alert.

1024-637_Blog_Christoph_Spix

Defective Image

1024-639_Blog_Christoph_Spix

The script marked the defective parts of the image compared to the should-be image.

Recap

Spix is not a full-blown GUI testing framework like Squish, but it fills a useful niche for embedded developers who want to:

  • Save time on repetitive input (like logins).
  • Share reproducible setup scripts with colleagues.
  • Perform lightweight visual regression testing in CI.
  • Interact with their applications on embedded devices remotely.

While there are limitations (e.g. manual wait times, lack of deep synchronization with UI states), Spix provides a powerful and flexible way to automate everyday development tasks - without having to alter your application logic.

If you’re tired of clicking the same buttons all day, give Spix a try. It might just save you hours of time and frustration in your embedded development workflow.

The post Automating Repetitive GUI Interactions in Embedded Development with Spix appeared first on KDAB.

The Variables To start a handshake, we need two public numbers that everyone knows: Base (g): 2 Modulus (p): 19 Step 1: The Private Secrets Two parties, Alice and Shiva, choose secret numbers (Private Keys).

Wednesday, 18 February 2026

Hey all! I'm CJ and I'm checking in with a midterm update on the Season of KDE task of automating data collection for the KDE promotional team.

The first term of the two for this Season of KDE task has mostly been a learning experience of what does and doesn't work when it comes to scraping data from the web, laying down our toolset and approach to data collection.

Three subtasks have resulted:

  1. Create a script that collects follower and post counts from several websites housing KDE's social media accounts
  2. Create a script that processes information from the Reddit Insights page for the KDE subreddit
  3. Create a script automating the evaluation of articles discussing KDE tools

The first two of those are mostly completed while the last one is in its research and planning phase. Both finished subtasks came with their own sets of challenges, techniques and tools that I'll detail separately.

Follower and post counts scraper

This is a script I discussed in my first blog post that scrapes follower and post counts from X (formerly known as Twitter), Mastodon, Bluesky, and Threads. The major updates to this script made since then are that it employs a more user and server-friendly usage method and that we've tackled a few issues that came up outside of the script's scraping. On the usage side I've added command-line arguments and an expectation for a JSON file containing the links to scrub from. This makes swapping out social media links easy as well as adding options for scaling up configuration of the script if any further development is needed.

At the point of writing the logic of the script has held up well but the data format we were outputting to, Open Document Format (ODF), wasn't friendly for our specific usage, which is something I touched on in that first blog post. In the end we decided the tools that interface with ODF were too unwieldy to work with from an automation and programmatic standpoint so we're looking into alternatives at the moment. One promising solution is KDE's LabPlot which has a good looking (but experimental) Python API and is FOSS. For now I've set the script up to output to a user-friendly JSON file until we resolve what tool will be leveraged for data analysis in the end.

Another issue came from the input-side of the script in the X/Twitter scraping portion. Many public Nitter instances implement bot-prevention I was unaware of that triggered on an attempted headless server run of the script. With that making simpler scraping methods difficult and also paying respect to those instances' desire not to be botted, I've decided to spin up our own local Nitter instance on the server which is running the script. Now scraping X/Twitter comes much more easily and with a lot less risk of failure.

KDE subreddit Insights scraper

Since that first week we've added another task, being the creation of a script that can add up the weekly influx of new visitors, unique page views, and members of the KDE subreddit utilizing the subreddit's Insight page. This script mostly challenged our ability to automate the login process for Reddit as the usual methods are prevented by browser verification tools.

Reddit implements some version of reCAPTCHA that utilizes a form of invisible reCAPTCHA on their login page. The method of implementation changes based off which version they use, but in the end a score grading the likelihood of a user being a bot or a human is returned to the website upon login. This means that simple HTTP requests are likely not enough to get the job done and that a level of interaction supplied using a browser automation framework is needed to handle the login process.

To that end, we chose to leverage the long-standing Selenium web browser automation framework. Selenium, and many browser-automation frameworks like it, works by launching a full-featured web browser to run its automated tasks. This introduces problems in running these scripts on a headless server but greatly simplifies bot-prevention thwarting and the loading of any JavaScript-sensitive page elements.

With Selenium automating our login process, the only challenge left was to process the HTML data retrieved. Reddit Insights presents its information in the form of bar charts that visualize the daily page views, unique visitors, and subscribers to a subreddit. Some small analysis of the page source revealed that the daily data populating the bar charts are stored with millisecond UNIX timestamp representations of those days. Using BeautifulSoup, it was very easy for me to grab that daily data using those timestamps and sum up the totals needed for our script.

The main challenge this script presents now is how we can get it running on a weekly basis in a headless server. The UI component is non-negotiable so the solution will very likely come in the form of server configuration.

Smaller updates

  • Investigated automation of NextCloud data uploads
  • Researched how to schedule scripts to run on an interval using systemd unit files
  • Wrote technical documentation on the purpose and usage of both scripts developed at the point of writing
  • Researched various alternative packages for performing HTTP requests and browser automation tasks

Future

Since the last two subtasks are complete logic-wise outside of any future issues we run into, a new one has been assigned as part of the data collection automation task. The KDE promo team collects various articles about KDE and software related to it and evaluates the contents of those articles as they relate to KDE and how they view whichever KDE tool they discuss. This evaluation process is performed manually which takes up time, so I've been tasked with developing some method of analyzing these articles in an automated fashion.

Along with that new subtask, solving the issues of running browser automation software on a server and what data evaluation software we'll target will greatly benefit us by expanding our options for deploying scripts made in this task and making their data immediately useful for the KDE promo team.

Lessons learned

It's been a lot of fun to tackle the first two tasks. I've had to pull from past experience with APIs, HTML, and HTTP that have been rotting in deeper parts of my brain as well as learn much more about how modern, full-featured websites deploy those tools. I'm a bit anxious about the problem of server deployment since I want these scripts to be as useful and maintainable as possible for the KDE promotion team, but I'm confident we'll find a solution and I'm sure it will feel very rewarding to solve.

Concerning the new subtask, this assignment is a departure from the first two and it's very likely a light and local AI/machine learning method will be looped into this process. That makes it exciting to tackle since it's so different from the last couple of subtasks and incorporates an entirely separate emerging field. I'm very much looking forward to rounding my skills with the new challenges this subtask presents.

Tuesday, 17 February 2026

In my last post, I made a solemn vow to not touch Kapsule for a week. Focus on the day job. Be a responsible adult.

Success level: medium.

I did get significantly more day-job work done than the previous week, so partial credit there. But my wife's mother and sister are visiting from Japan, and they're really into horror movies. I am not. So while they were watching people get chased through dark corridors by things with too many teeth, I was in the other room hacking on container pipelines with zero guilt. Sometimes the stars just align.

coding while untold horrors occur in the next room

Here's what came out of that guilt-free hack time.

Konsole integration: it's actually done

containers in new tab menu

The two Konsole merge requests from the last post—!1178 (containers in the New Tab menu) and !1179 (container association with profiles)—are merged. They're in Konsole now. Shipped.

Building on that foundation, I've got two more MRs up:

!1182 adds the KapsuleDetector—the piece that actually wires Kapsule into Konsole's container framework. It uses libkapsule-qt to list containers over D-Bus and OSC 777 escape sequences for in-session detection, following the same pattern as the existing Toolbox and Distrobox detectors. It also handles default containers: even if you haven't created any containers yet, the distro-configured default shows up in the menu so you can get into a dev environment in one click.

!1183 is a small quality-of-life addition: when containers are present, a Host section appears at the top of the container menu showing your machine's hostname. Click it, and you get a plain host terminal. This matters because once you set a container as your default, you need a way to get back to the host without going through settings. Obvious in hindsight.

The OSC 777 side of this lives in Kapsule itself—kapsule enter now emits container;push / container;pop escape sequences so Konsole knows when you've entered or left a container. This is how the tab title and container indicator stay in sync.

Four merge requests across two repos (Konsole and Kapsule) to get from "Konsole doesn't know Kapsule exists" to "your containers are in the New Tab menu and your terminal knows when you're inside one." Not bad for horror movie time.

Configurable host mounts: the trust dial is real

In the last post, I talked about making filesystem mounts configurable—turning the trust model into a dial rather than a switch. That's shipped now.

--no-mount-home does what it says—your home directory stays on the host, the container gets its own. --custom-mounts lets you selectively share specific directories. And --no-host-rootfs goes further, removing the full host filesystem mount entirely and providing only the targeted socket mounts needed for Wayland, audio, and display to work.

The use case I had in mind was sandboxing AI coding agents and other tools you don't fully trust with your home directory. But it's also useful for just keeping things clean—some containers don't need to see your host files at all.

Snap works now

Here's a screenshot of Firefox running in a Kapsule container on KDE Linux, installed via Snap:

screenshot of firefox in snap in kapsule

I expected this one to be a multi-day ordeal. It wasn't.

Snap apps—like Firefox on Ubuntu—run in their own mount namespace, and snap-update-ns can't follow symlinks that point into /.kapsule/host/. So our Wayland, PipeWire, PulseAudio, and X11 socket symlinks were invisible to anything running under Snap, resulting in helpful errors like "Failed to connect to Wayland display."

The fix was straightforward: replace all those symlinks with bind mounts via nsenter. Bind mounts make the sockets appear as real files in the container's filesystem, so Snap's mount namespace setup handles them correctly. That was basically it.

While I was in there, I batched all the mount operations into a single nsenter call instead of running separate incus exec invocations per socket. That brought the mount setup from "noticeably slow" to "instant"—roughly 10-20x faster on a cold cache. And the mount state is now cached per container, so subsequent kapsule enter calls skip the work entirely.

NVIDIA GPU support (experimental)

jensen huang with nvidia logo and chip

This one's interesting both technically and in terms of where it's going.

Kapsule containers are privileged by design—that's what lets us do nesting, host networking, and all the other things that make them feel like real development environments. The problem is that upstream Incus and LXC both reject their NVIDIA runtime integration on privileged containers. The upstream LXC hook expects user-namespace UID/GID remapping, and the default codepath wants to manage cgroups for device isolation. Neither applies to our containers.

So I wrote a custom LXC mount hook that runs nvidia-container-cli directly with --no-cgroups (privileged containers have unrestricted device access anyway) and --no-devbind (Incus's GPU device type already passes the device nodes through). This leaves nvidia-container-cli with exactly one job: bind-mount the host's NVIDIA userspace libraries into the container rootfs so CUDA, OpenGL, and Vulkan work without the container image shipping its own driver stack.

There's a catch, though. On Arch Linux, the injected NVIDIA libraries conflict with mesa packages. The container's package manager thinks mesa owns those files, and now there are mystery bind-mounts shadowing them. It works, but it's ugly and will cause problems during package updates. I hit this on Arch first, but I'd be surprised if other distros don't have the same issue—any distro where mesa owns those library paths is going to complain.

So NVIDIA support is disabled by default for now. The plan: build Kapsule-specific container images that ship stub packages for the conflicting files, and have images opt-in to NVIDIA driver injection via metadata. Two independent flags control the behavior: --no-gpu disables device passthrough entirely (still on by default), and --nvidia-drivers enables the driver injection.

Architecture: pipelines all the way down

turtles all the way down meme

The biggest behind-the-scenes change in v0.2.1 is the complete restructuring of container creation. The old container_service.py was a 1,265-line monolith that did everything sequentially in one massive function. It's gone now.

In its place is a decorator-based pipeline system. Container creation is a series of composable steps, each a standalone async function that handles one concern:

Pre-creation:     validate → parse image → build config → store options → build devices
Incus API call:   create instance
Post-creation:    host network fixups → file capabilities → session mode
User setup:       mount home → create account → configure sudo → custom mounts → host dirs → enable linger → mark mapped

Each step is registered with an explicit order number and gaps of 100 between steps, so inserting new functionality doesn't require renumbering everything. The decorator handles sorting by priority with stable tie-breaking, so import order doesn't matter.

This pattern worked well enough that I plan to extend it to other large operations—delete, start, stop—as they accumulate their own pre/post logic.

On the same theme of "define it once, use it everywhere": container creation options are now defined in a single Python schema that serves as the source of truth for the daemon's validation, the D-Bus interface (which now uses a{sv} variant dicts, so adding an option never changes the method signature), and the C++ CLI's flag generation. Add a new option in Python, recompile the CLI, and you've got a --flag with help text and type validation. Zero manual C++ work.

The long-term plan is to use this same schema to dynamically generate the graphical UI in a future KCM. Define the option once, get the CLI flag, the D-Bus parameter, the daemon validation, and the Settings page widget—all from the same schema.

First external contributor

Marie Ramlow (@meowrie) submitted a fix for PATH handling on NixOS—the first external contribution to Kapsule. I don't have a NixOS setup to test it on, so this one's on trust. That's open source for you: someone shows up, fixes a problem you can't even reproduce, and you merge it with gratitude and a prayer.

Testing

The integration test suite grew substantially. New tests cover host mount modes, custom mount options, OSC 777 escape sequence emission, and socket passthrough. The test runner now does two full passes—once with the default full-rootfs mount and once with --no-host-rootfs—to verify both configurations work.

Bugs caught during testing that would have been embarrassing in production: a race condition in the Incus client where sequential device additions could clobber each other (the client wasn't waiting for PUT operations to complete), and Alpine containers failing because they don't ship /etc/sudoers.d by default.

CI/CD: of all the things to break

oil pipeline fire

I finally built out the CI/CD pipelines. They use the same kde-linux-builder image that builds KDE Linux itself—mainly because it's one of the few CI images with sudo access enabled, which we need for Incus operations.

The good news: the pipeline successfully builds the entire project, packages it into a sysext, deploys it to a VM, and runs the integration tests. That whole chain works. I was pretty pleased with myself for about ten minutes.

The bad news: when the first test tries to actually create a container, the entire CI job dies. Not "the test fails." Not "the runner reports an error." The whole thing just... stops. No exit code, no error message, no logs after that point. Nothing.

I'm fairly sure it's causing a kernel panic in the CI runner's VM. Which is, you know, not great.

Debugging this has been miserable. I can't get any logs after the panic because there are no logs—the kernel is gone. I tried adding debug prints before each step in the container creation pipeline to isolate exactly where it dies. The prints don't come through either, probably because of output buffering, or maybe the runner agent doesn't get a chance to stream the output to GitLab before the entire VM goes down.

The weird part: it's not a nested virtualization issue. Regular Incus works fine on the same runner—you can create containers interactively, no problem. And it doesn't reproduce on KDE Linux at all. Something about the specific combination of the CI environment and Kapsule's container creation path is triggering it, and I have no way to see what.

I've shelved this for now. The pipeline is there, the build and deploy stages work, and the tests would work if the runner didn't kernel panic when Kapsule tries to create a container. If anyone reading this has ideas, I'm all ears.

What's next: custom container images

shipping containers

The biggest item on my plate is custom container images. Right now, Kapsule uses stock distribution images from the Incus image server. They work, but they're not optimized for our use case—things like the NVIDIA stub packages I mentioned above need to live somewhere, and "just install them at container creation time" adds latency and fragility.

Incus uses distrobuilder for image creation, so the plan is straightforward: image definitions live in a directory in the Kapsule repo, a CI pipeline invokes distrobuilder to build them, and the images get published to a server.

The "published to a server" part is where it gets political. I talked to Ben Cooksley about hosting Kapsule images on KDE infrastructure, and he's—understandably—not yet convinced that Kapsule needs its own image server. It's a fair pushback. This is all still experimental, and spinning up image hosting infrastructure for a project that might change direction is a reasonable thing to be cautious about.

So for now, I'll host the images on my own server. They probably won't be the default, since the server is in the US and download speeds won't be great for everyone. But they'll be available for testing and for anyone who wants the NVIDIA integration or other Kapsule-specific tweaks. I'll bug Ben again when the image story is more fleshed out and there's a clearer case for why KDE infrastructure should host them.

Beyond that: get the Konsole MRs (!1182 and !1183) reviewed and merged, and figure out why CI kills the kernel. The usual.