14 Jan 2026

feedPlanet KDE | English

REST API Development with Qt 6

This post describes an experiment using Qt 6.7's REST APIs to explore Stripe's payment model, and what I learned building a small desktop developer tool.

Recent Qt releases have included several conveniences for developing clients of remote REST APIs. I recently tried it out with the Stripe payments REST API to get to grips with the Qt REST API in the real world. The overloading of the term API is unhelpful, I find, but hopefully not too confusing here.

As with almost everything I try out, I created Qt desktop tooling as a developer aid to exploring the Stripe API and its behavior. Naming things is hard, but given that I want to put a "Q" in the name, googling "cute stripes" gives lots of hits about fashion, and the other too-obvious-to-say pun, I've pushed it to GitHub as "Qashmere":

setAlternatingRowColors(true);

Developers using REST APIs will generally be familiar with existing tooling such as Postman and Bruno, for synthesizing calls to collections of REST APIs. Indeed, Qashmere uses the Stripe Postman JSON definition to present the collection of APIs and parameters. Such tools have scripting interfaces and state to create workflows that a client of the REST API needs to support, like "create a payment, get the id of the payment back from the REST API and then cancel the payment with the id", or "create a payment, get the id of the payment back from the REST API and then confirm it by id with a given credit card".

So why create Qashmere? In addition to REST APIs, Stripe maintains objects which change state over time. The objects remain at REST until acted on by an external force, and when such an action happens a notification is sent to clients about those state changes, giving them a chance to react. I wanted to be able to collect the REST requests/responses and the notified events and present them as they relate to the Stripe objects. Postman doesn't know about events or about Stripe objects in particular, except that it is possible to write a script in Postman to extract the object which is part of a JSON payload. Postman also doesn't know that if a Payment Intent is created, there are a subset of next steps which could be in a workflow, such as cancel, capture or confirm payment etc.

Something that I discovered in the course of trying this out is that when I confirm a Payment Intent, a new Charge object is created and sent to me with the event notification system. Experimental experiences like that help build intuition.

Stripe operates with real money, but it also provides for sandboxes where synthetic payments, customers etc can be created and processed with synthetic payment methods and cards. As Qashmere is only useful as a developer tool or learning aid, it only works with Stripe sandboxes.

Events from Stripe are sent to pre-configured web servers owned by the client. The web servers need to have a public IP address, which is obviously not appropriate for a desktop application. A WebSocket API would be more suitable and indeed the stripe cli tool uses a WebSocket to receive events, but the WebSocket protocol is not documented or stable. Luckily the stripe cli tool can be used to relay events to another HTTP server, so Qashmere runs a QHttpServer for that purpose.

Implementation with Qt REST API

The QRestReply wraps a QNetworkReply pointer and provides convenience API for accessing the HTTP return code and for creating a QJsonDocument from the body of the response. It must be created manually if using QNetworkAccessManager directly. However the new QRestAccessManager wraps a QNetworkAccessManager pointer, again to provide convenience APIs and overloads for making requests that are needed in REST APIs (though some less common verbs like OPTIONS and TRACE are not built-in). The QRestAccessManager has conveniences like overloads that provide a way to supply callbacks which already take the QRestReply wrapper object as a parameter. If using a QJsonDocument request overload, the "application/json" Content-Type is automatically set in the header.

One of the inconveniences of QRestAccessManager is that in Qashmere I use an external definition of the REST API from the Postman definition which includes the HTTP method. Because the QRestAccessManager provides strongly typed API for making requests I need to do something like:

if (method == "POST") {
rest.post(request, requestData, this, replyHandler);
} else if (method == "GET") {
rest.get(request, this, replyHandler);
} else if (method == "DELETE") {
rest.deleteResource(request, this, replyHandler);
}

There is a sendCustomRequest class API which can be used with a string, but it does not have an overload for QJsonDocument, so the convenience of having the Content-Type header set is lost. This may be an oversight in the QRestAccessManager API.

Another missing feature is URL parameter interpolation. Many REST APIs are described as something like /v1/object/:object_id/cancel, and it would be convenient to have a safe way to interpolate the parameters into the URL, such as:

QUrl result = QRestAccessManager::interpolatePathParameters(
"/v1/accounts/:account_id/object/:object_id/cancel", {
{"account_id", "acc_1234"},
{"object_id", "obj_5678"}
}
);

This is needed to avoid bugs such as a user-supplied parameter containing a slash for example.

Coding Con Currency

In recent years I've been writing and reading more Typescript/Angular code which consumes REST services, and less C++. I've enjoyed the way Promises work in that environment, allowing sequences of REST requests, for example, to be easy to write and read. A test of a pseudo API could await on requests to complete and invoke the next one with something like:

requestFactory.setBaseURL("http://some_service.com");
async testWorkflow(username: string, password: string) {
const loginRequest = requestFactory.makeRequest("/login");
const loginRequestData = new Map();
loginRequestData.setParam("username", username);
loginRequestData.setParam("password", password);
const loginResponse = await requestAPI.post(
loginRequest, loginRequestData);
const bearerToken = loginResponse.getData();
requestAPI.setBearerToken(bearerToken);
const listingRequest = requestFactory.makeRequest("/list_items");
const listingResponse = await requestAPI.get(listingRequest);
const listing = JSON.parse(listingResponse.getData());
const firstItemRequest = requestFactory.makeRequest(
"/retrieve_item/:item_id",
{
item_id: listing[0].item_id
}
);
const firstItem = await requestAPI.get(firstItemRequest);
}

The availability of async functions and the Promise to await on make a test like this quite easy to write, and the in-application use of the API uses the same Promises, so there is little friction between application code and test code.

I wanted to see if I can recreate something like that based on the Qt networking APIs. I briefly tried using C++20 coroutines because they would allow a style closer to async/await, but the integration friction with existing Qt types was higher than I wanted for an experiment.

Using the methods in QtFuture however, we already have a way to create objects representing the response from a REST API. The result is similar to the Typescript example, but with different ergonomics, using .then instead of the async and await keywords.

struct RestRequest
{
QString method;
QString requestUrl;
QHttpHeaders headers;
QHash<QString, QString> urlParams;
QUrlQuery queryParams;
std::variant<QUrlQuery, QJsonDocument> requestData;
};
struct RestResponse
{
QJsonDocument jsonDoc;
QHttpHeaders headers;
QNetworkReply::NetworkError error;
QUrl url;
int statusCode;
};
QFuture<RestResponse> makeRequest(RestRequest restRequest)
{
auto url = interpolatePathParameters(
restRequest.requestUrl,
restRequest.urlParams);
auto request = requestFactory.createRequest(url);
auto requestBodyDoc = extractRequestContent(restRequest.requestData);
auto requestBody = requestBodyDoc.toJson(QJsonDocument::Compact);
auto reply = qRestManager.sendCustomRequest(request,
restRequest.method.toUtf8(),
requestBody,
&qnam,
[](QRestReply &) {});
return QtFuture::connect(reply, &QNetworkReply::finished).then(
[reply]() {
QRestReply restReply(reply);
auto responseDoc = restReply.readJson();
if (!responseDoc) {
throw std::runtime_error("Failed to read response");
}
RestResponse response;
response.jsonDoc = *responseDoc;
response.statusCode = restReply.httpStatus();
response.error = restReply.error();
response.headers = reply->headers();
response.url = reply->url();
return response;
}
);
}

The QRestAccessManager API requires the creation of a dummy response function when creating a custom request because it is not really designed to be used this way. The result is an API accepting a request and returning a QFuture with the QJsonDocument content. While it is possible for a REST endpoint to return something else, we can follow the Qt philosophy of making the most expected case as easy as possible, while leaving most of the rest possible another way. This utility makes writing unit tests relatively straightforward too:

RemoteAPI remoteApi;
remoteApi.setBaseUrl(QUrl("https://dog.ceo"));
auto responseFuture = remoteApi.makeRequest(
{"GET",
"api/breed/:breed/:sub_breed/images/random",
{},
{
{"breed", "wolfhound"},
{"sub_breed", "irish"}
}});
QFutureWatcher<RestResponse> watcher;
QSignalSpy spy(&watcher, &QFutureWatcherBase::finished);
watcher.setFuture(responseFuture);
QVERIFY(spy.wait(10000));
auto jsonObject = responseFuture.result().jsonDoc.object();
QCOMPARE(jsonObject["status"], "success");
QRegularExpression regex(
R"(https://images\.dog\.ceo/breeds/wolfhound-irish/[^.]+.jpg)");
QVERIFY(regex.match(jsonObject["message"].toString()).hasMatch());

The result is quite similar to the Typescript above, but only because we can use spy.wait. In application code, we still need to use .then with a callback, but we can additionally use .onFailed and .onCanceled instead of making multiple signal/slot connections.

With the addition of QtFuture::whenAll, it is easy to make multiple REST requests at once and react when they are all finished, so perhaps something else has been gained too, compared to a signal/slot model:

RemoteAPI remoteApi;
remoteApi.setBaseUrl(QUrl("https://dog.ceo"));
auto responseFuture = remoteApi.requestMultiple({
{
"GET",
"api/breeds/list/all",
},
{"GET",
"api/breed/:breed/:sub_breed/images/random",
{},
{{"breed", "german"}, {"sub_breed", "shepherd"}}},
{"GET",
"api/breed/:breed/:sub_breed/images/random/:num_results",
{},
{{"breed", "wolfhound"},
{"sub_breed", "irish"},
{"num_results", "3"}}},
{"GET", "api/breed/:breed/list", {}, {{"breed", "hound"}}},
});
QFutureWatcher<QList<RestResponse>> watcher;
QSignalSpy spy(&watcher, &QFutureWatcherBase::finished);
watcher.setFuture(responseFuture);
QVERIFY(spy.wait(10000));
auto four_responses = responseFuture.result();
QCOMPARE(four_responses.size(), 4);
QCOMPARE(four_responses[0].jsonDoc.object()["status"], "success");
QVERIFY(four_responses[0].jsonDoc.object()["message"].
toObject()["greyhound"].isArray());
QRegularExpression germanShepherdRegex(
R"(https://images.dog.ceo/breeds/german-shepherd/[^.]+.jpg)");
QCOMPARE(four_responses[1].jsonDoc.object()["status"], "success");
QVERIFY(germanShepherdRegex.match(
four_responses[1].jsonDoc.object()["message"].toString()).hasMatch());
QRegularExpression irishWolfhoundRegex(
R"(https://images.dog.ceo/breeds/wolfhound-irish/[^.]+.jpg)");
QCOMPARE(four_responses[2].jsonDoc.object()["status"], "success");
auto irishWolfhoundList =
four_responses[2].jsonDoc.object()["message"].toArray();
QCOMPARE(irishWolfhoundList.size(), 3);
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[0].toString()).
hasMatch());
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[1].toString()).
hasMatch());
QVERIFY(irishWolfhoundRegex.match(irishWolfhoundList[2].toString()).
hasMatch());
QCOMPARE(four_responses[3].jsonDoc.object()["status"], "success");
auto houndList = four_responses[3].jsonDoc.object()["message"].toArray();
QCOMPARE_GE(houndList.size(), 7);
QVERIFY(houndList.contains("afghan"));
QVERIFY(houndList.contains("basset"));
QVERIFY(houndList.contains("blood"));
QVERIFY(houndList.contains("english"));
QVERIFY(houndList.contains("ibizan"));
QVERIFY(houndList.contains("plott"));
QVERIFY(houndList.contains("walker"));

setAutoDeleteReplies(false);

I attempted to use new API additions in recent Qt 6 versions to interact with a few real-world REST services. The additions are valuable, but it seems that there are a few places where improvements might be possible. My attempt to make the API feel closer to what developers in other environments might be accustomed to had some success, but I'm not sure QFuture is really intended to be used this way.
Do readers have any feedback? Would using QCoro improve the coroutine experience? Is it very unusual to create an application with QWidgets instead of QML these days? Should I have used PyQt and the python networking APIs?

14 Jan 2026 11:58am GMT

Haruna 1.7







Haruna version 1.7.1 is released.

playlist advanced sorting and grouping showcase
playlist advanced sorting and grouping

flathub logo

Windows version:

Availability of other package formats depends on your distro and the people who package Haruna.

If you like Haruna then support its development: GitHub Sponsors | Liberapay | PayPal

Feature requests and bugs should be posted on bugs.kde.org, ignoring the bug report template can result in your report being ignored.


Known issues

The animation for the playlist can be stuttery/slow when playback is active. You can improve it by creating two custom commands that run on startup set override-display-fps 75 (replace 75 with your monitor's refresh rate) and set video-sync display-resample.

These don't work for variable refresh rate monitors.

Changelog

1.7.1

Bugfixes

1.7.0

Features

Playlist

Other

Bugfixes

14 Jan 2026 10:00am GMT

13 Jan 2026

feedPlanet KDE | English

KDE Gear 26.04 release schedule

This is the release schedule the release team agreed on

https://community.kde.org/Schedules/KDE_Gear_26.04_Schedule

Dependency freeze is in around 7 weeks (March 5) and feature freeze one
after that. Get your stuff ready!

13 Jan 2026 11:43pm GMT

KDE Ni! OS – Plasma Login Manager teaser

Just a teaser this time.

I've read somewhere that Fedora will be the first distribution to replace SDDM with Dave's brand new Plasma Login Manager.

Will Ni! OS be the first non-distribution to do the same?

And if it does, will it become a distribution as it distributes yet another package not available in vanilla NixOS? :)

Plasma Login Manager on Ni! OS
Plasma Login Manager on Ni! OS

13 Jan 2026 6:35pm GMT

KDE Plasma 6.6 Beta Release

Here are the new modules available in the Plasma 6.6 beta:

Some important features and changes included in 6.6 beta are highlighted on KDE community wiki page.

View full changelog

13 Jan 2026 12:00am GMT

KDE Plasma 6.5.5, Bugfix Release for January

Tuesday, 13 January 2026. Today KDE releases a bugfix update to KDE Plasma 6, versioned 6.5.5.

Plasma 6.5 was released in October 2025 with many feature refinements and new modules to complete the desktop experience.

This release adds a month's worth of new translations and fixes from KDE's contributors. The bugfixes are typically small but important and include:

View full changelog

13 Jan 2026 12:00am GMT

12 Jan 2026

feedPlanet KDE | English

2025 Musically Wrapped

I have a paper calendar. It hangs on the wall. I draw things on it, like 🎜 Kladderadatch, to remind me where to go of an evening (or this afternoon). At the end of the year, with that calendar and my ticket history from Doornroosje (a music podium in Nijmegen) I can reconstruct my concert visits of the year. Here's my year wrapped.

Not in Nijmegen:

Ones I missed (but did have tickets):

That's 46 concerts this year. I do try to see something every week. In principle I don't listen to stuff in advance, I just go and find out what it is once the band starts. I have a strong preference for Merleyn, the smallest of the Doornroosje venues, because everything is close-by and personal. The beer is better there, also. Punk has the best odds of making me happy on an evening, but I'm glad I go to random other stuff to broaden my horizons. I have punk, jazz, ska and rap lined up for the next three months, and also Green Milk from the Planet Orange, whatever genre that is.

12 Jan 2026 11:00pm GMT

11 Jan 2026

feedPlanet KDE | English

Game Jam for Free Software Desktop Games

The folks at GNU/Linux València are organizing a Game Jam focused on Free Software Desktop Games.

You can see the details here: https://itch.io/jam/lliurejam

Maybe we could take the opportunity to try to revive a bit the very very very dormant KDE Games community?

Though we have the basic games covered already so someone would have to come up with an idea of what to do first :D

11 Jan 2026 11:46am GMT

10 Jan 2026

feedPlanet KDE | English

Updated Tellico Handbook

Online publication of the documentation for many KDE applications has been updated to docs.kde.org. Tellico's current handbook can be found there.

10 Jan 2026 10:50pm GMT

KJournald Update January 2026

A surprising long time passed since my last status update about KJournald. So it's time again to shed some light on the recent changes.

KJournald is a KDE project that provides graphical browsing UI for journald log databases. For those who never heard the term "journald", journald is the system logging service of systemd and it is found in most modern Linux systems. This means, in the journald databases one can find all the system log messages about important incidents happening on a system, which make it very important for system admins but also for all technical users who want to analyze when something is not working correctly on their systems.

The kjournald-browser provides a Qt and Kirigami based UI to efficiently browse and filter those logs (note: there exist different tools for that, even systemd provides its own command line tool "journalctl"). The focus of kjournald-browser are the following use cases:

Since my last blog post, the kjournald-browser application became part of the regular KDE gear releases and nowadays is packages by e.g. Fedora and Suse; unfortunately, it is still not packaged on Debian or Ubuntu yet - if you want to do it and need support please reach out to me! At the moment, also the packaging as Flatpak application on Flathub is ongoing. But already since a long time though, the KDE Flatpak nightly builds provide the latest state of the app.

With the last major release 25.12.0, a few new cool features were added:

One feature was slightly too late for this release, but is already ready for the next:

Since 2026 is still young, there are a few features on the roadmap of this year. The two most important ones in my opinion are:

10 Jan 2026 6:19pm GMT

I love KDE too much to retire! Snap beta releases trickling in. Stay tuned.

A short but sweet note to say I am coming out of my short retirement to help with snaps again. My time is extremely limited, however we are working hard on getting snaps on CI and I have some newer snaps in -beta trickling in for testing. You must install kf6-core24 from beta as well to test them ( this will likely break older kde snaps in the process so beware. ) This is slow going as I work on them during my hour lunch at day job and spare stolen moments. KDE is coming up on its 30th birthday!!! How cool is that!

I ❤ KDE

Like my work? Consider a donation. Thank you!

Donate

10 Jan 2026 4:54pm GMT

This Week in Plasma: car of the year edition

Welcome to a new issue of This Week in Plasma!

Let's thank Lubos Krystynek, Rafal Krawczyk, and John Veness for stepping up to help with this week's issue. Thanks, guys!

This week, the first car running KWin won the "Car of the Year" award. Yes, really - KDE in the car! Here's KDE's Victoria Fischer talking about it at Qt World Summit 2023:

KDE in the Kar!

Almost all of these posts end with "KDE has become important in the world…" and I think this is a good reminder that it's true, not just some empty platitude. KDE is important. And all of you building or using KDE's software are important, too.

But KDE is not only important to cars; we're incredibly important to computers! And on that subject, some really nice features and user interface improvements landed for the upcoming Plasma 6.6 release. The hard feature freeze is coming up soon, at which point we'll move into full bug-fixing and polishing mode.

But until then, enjoy some juicy new goodies! Check it out:

Notable New Features

Plasma 6.6.0

You can now save your current visual settings as a new global theme! (Vlad Zahorodnii, plasma-desktop MR #6097)

Global Themes page in System Settings showing the opportunity to create a new global theme from the current visual settings

Added a "Forget device" action to the Bluetooth system tray widget, allowing users to remove paired devices without opening System Settings. (Andrew Gigena, KDE Bug #434691)

You can now search for processes in System Monitor based on their full command-line invocation when the "Command" column is visible. (Alexey Rochev, KDE Bug #448331)

On supported systems, the logout screen now mentions when the system will restart into a different operating system or boot option after it reboots. (Nikolay Kochulin, plasma-workspace MR #5469)

Logout screen showing that a different OS will be booted into after restart

Notable UI Improvements

Plasma 6.6.0

The Power and Battery widget now tells you what specific power management actions apps are blocking, instead of assuming that they're all blocking both sleep and screen locking. (Jakob Petsovits, KDE Bug #418433)

Power and Battery widget telling you what each app is actually blocking

System Settings' Thunderbolt page now hides itself when the device doesn't support Thunderbolt. (Alexander Wilms, plasma-thunderbolt MR #47)

When there are many windows open, the Task Manager widget will now scroll to the active one when you open its window thumbnail list. (Christoph Wolk, KDE Bug #499716)

Notifications no longer waste space showing the same icon in two places. (Kai Uwe Broulik, plasma-workspace MR #6151)

Spectacle now remembers the size (and on X11, also the position) of its main window across launches. (Aviral Singh, KDE Bug #499652)

Made multiple UI improvements to the "Configure Columns" dialog in System Monitor. (Arjen Hiemstra, plasma-systemmonitor MR #405)

System Monitor’s new “Configure Columns” dialog

In the Weather Report widget, when a weather station isn't reporting the current wind speed, the widget now says it doesn't know the wind speed, rather than claiming it's "calm". (Tobias Fella, kdeplasma-addons MR #969)

The Kickoff Application Menu widget now does a better job of handling a huge number of favorite apps. Now the favorites column eventually becomes scrollable, instead of letting icons overlap. (Christoph Wolk, KDE Bug #424067)

You can now find System Settings' Wallpaper page by searching for "desktop background" and some other related terms. (Shubham Arora, plasma-workspace MR #6152)

Frameworks 6.23

Made it possible to see more items at once in the "Get New [thing]" dialogs. (Nate Graham, frameworks-knewstuff MR #380)

More items visible at a time in the Get New Stuff dialogs

Open/Save dialogs now use relative-style date formatting for recent dates and times, which matches how Dolphin shows them. (Méven Car, frameworks-kio MR #2103)

Folders that show thumbnails of their contents now refresh the thumbnail immediately when any of those files are removed. (Akseli Lahtinen, KDE Bug #497259)

Notable Bug Fixes

Plasma 6.5.5

Fixed a strange issue that broke key repeat only in the Brave web browser. (Nicolas Fella, KDE Bug #513637)

Fixed an issue that could make the panel configuration dialog appear on the wrong screen with certain panel and screen arrangements. (Aleksey Rochev, plasma-workspace MR #6140)

Fixed two issues with the "Show Alternatives" popup: one that made it get cut off outside of the screen area for widgets positioned on certain areas of the desktop, and another that made it not disappear when it lost focus. (Aleksey Rochev, KDE Bug #511188 and KDE Bug #511187)

Plasma 6.6.0

Fixed an issue that made Plasma quit when you disconnected the last screen. (Xaver Hugl, KDE Bug #513003)

Fixed an issue with the Applications table on System Monitor's Overview page being blurry with certain scale factors. We had already previously fixed this, but it turned out there were more remaining cases where it still happened, so this should take care of the rest! (Arjen Hiemstra, KDE Bug #445759)

Notable in Performance & Technical

Plasma 6.6.0

Implemented support in Plasma for the up-and-coming oo7 Secret Service provider. (Marco Martin and Harald Sitter, plasma-workspace MR #6109)

Fixed a hilarious issue that caused the wallpaper to bounce a tiny bit with certain fractional scale factors on secondary screens using direct scan-out while on a very recent kernel version. (Xaver Hugl, KDE Bug #513277)

How You Can Help

KDE has become important in the world, and your time and contributions have helped us get there. As we grow, we need your support to keep KDE sustainable.

You can help KDE by directly getting involved. Donating time is actually more impactful than donating money. Each contributor makes a huge difference in KDE - you are not a number or a cog in a machine! You don't have to be a programmer, either; many other opportunities exist.

For example, helping out to write these posts is warmly appreciated. Anyone interested in getting involved should check out the evolving documentation on the topic.

You can also help out by making a donation! This helps cover operational costs, salaries, travel expenses for contributors, and in general just keep KDE bringing Free Software to the world.

To get a new Plasma feature or a bugfix mentioned here, feel free to push a commit to the relevant merge request on invent.kde.org.

10 Jan 2026 12:01am GMT

09 Jan 2026

feedPlanet KDE | English

Web Review, Week 2026-02

Let's go for my web review for the week 2026-02.


How Github monopoly is destroying the open source ecosystem

Tags: tech, foss, community, ecosystem, github

Github is definitely entrenched by now. Students and beginners hardly look for projects outside of it. Sad state of affair.

https://ploum.net/2026-01-05-unteaching_github.html


Rust At Scale: Scaleway's Big Bet To Become THE European Hyperscaler

Tags: tech, cloud, business, hardware, rust

Wondering what's on the mind of people working on an hyperscaler? This podcast and its transcript gives good insights.

https://filtra.io/rust/interviews/scaleway-jan-26


'Bizarro World'

Tags: tech, gaming, culture

An odd but interesting article. When a journalist randomly discovers that his wife is the best Tetris player in the world.

https://archive.boston.com/news/globe/magazine/articles/2007/08/19/bizarro_world/


Everything You Need to Know About Email Encryption in 2026

Tags: tech, email, security, privacy, politics, gafam

Email encryption is indeed still an open issue. There's no fix in sight for it. It's mostly a lack of political will though, so none of the big players are going to change anything.

https://soatok.blog/2026/01/04/everything-you-need-to-know-about-email-encryption-in-2026/


Improving the Flatpak Graphics Drivers Situation

Tags: tech, system, linux, flatpak, graphics

Interesting point… What to do when there's no good option in the application runtime for the needed graphics drivers and kernel combination?

https://blog.sebastianwick.net/posts/flatpak-graphics-drivers/


Functors, Applicatives, and Monads: The Scary Words You Already Understand

Tags: tech, functional, programming, type-systems, learning

Functional programming is made scary due to its jargon. But it doesn't have to be this way.

https://cekrem.github.io/posts/functors-applicatives-monads-elm/


Python Numbers Every Programmer Should Know

Tags: tech, python, performance

A very comprehensive view of Python memory consumption and the speed of common operations. Some of the numbers are higher than I expected.

https://mkennedy.codes/posts/python-numbers-every-programmer-should-know/


Stop Forwarding Errors, Start Designing Them

Tags: tech, rust, failure, debugging, monitoring, logging

Error handling is still not a properly solved problem in my opinion. At least the Rust community discusses the topic quite a bit. This is good inspiration for other ecosystems as well I think.

https://fast.github.io/blog/stop-forwarding-errors-start-designing-them/


Embassy

Tags: tech, embedded, rust

Looks like an interesting framework for embedded projects.

https://embassy.dev/


[uv] OnceMap: Rust Pattern for Running Concurrent Work Exactly Once

Tags: tech, rust, performance, design, multithreading

More interesting design ideas in uv. Didn't know about the dashmap crate they're using here it looks like a nice one too.

https://codepointer.substack.com/p/uv-oncemap-rust-pattern-for-running


Getting Real With LLMs

Tags: tech, ai, machine-learning, copilot, architecture, complexity

This looks like an interesting way to frame problems. It can give an idea of how likely they can be tackled with LLMs. It also shows that the architecture and the complexity greatly matter.

https://www.giladpeleg.com/blog/getting-real-with-llms


Coupling from a big-O perspective

Tags: tech, programming, complexity, design

OK maybe a longer piece than it should be. Still the idea is interesting. Clearly you want to mind the O(n) coupling in this context.

https://blog.ploeh.dk/2026/01/05/coupling-from-a-big-o-perspective/


We Need to Stop Calling Everything a Mock

Tags: tech, learning, tests, tdd

Indeed, the terminology has been greatly confused. I think I'll die on this particular hill though. I think it's important to name things properly. That said the trick of going through a verb might just work?

https://coding-is-like-cooking.info/2026/01/we-need-to-stop-calling-everything-a-mock/


Stop Guessing, Start Improving: Using DORA Metrics and Process Behavior Charts

Tags: tech, processes, metrics, data

Interesting short article. Shows the use of DORA metrics and process behavior charts. This is a good way to test hypothesis and see the impact of processes changes or introduction of new practices. It needs to be done over time and be patient of course.

https://www.infoq.com/articles/DORA-metrics-PBCs/


Improve Your Work System

Tags: management, organisation, team

Good questions to consider to gauge how you work. Can improve the organisation if you really get to the bottom of it.

https://www.congruentchange.com/improve-your-work-system/


worstofbreed.net - We make bad software

Tags: tech, complexity, satire, funny

This is a very nice satire website about the problems in our industry. Want to work in a resume driven context? Here is how!

https://worstofbreed.net/


Why Are There No Holes Around Trees?

Tags: science, biology

The biology of trees is just fascinating. And there's so much we still don't know about it.

https://www.youtube.com/watch?v=pHJIhxZEoxg



Bye for now!

09 Jan 2026 6:32pm GMT

QtNat – Open your port with Qt

QtNat is a lightweight C++ library built with Qt 6 that simplifies NAT port mapping using UPnP (Universal Plug and Play). It is designed to help developers easily expose local services to external networks without requiring manual router configuration for users.

By leveraging UPnP, QtNat automatically communicates with compatible routers to create port forwarding rules at runtime. This makes it particularly useful for peer-to-peer applications, multiplayer games, remote access tools, and any software that needs reliable inbound connectivity behind a NAT.

QtNat provides a simplified API to do all steps automatically: discovery and mapping. This has been tested on my local device. Feel free to test it and improve it.

Use it

UpnpNat nat;
QObject::connect(&nat, &UpnpNat::statusChanged, [&nat, &app]() {
switch(nat.status())
{
case UpnpNat::NAT_STAT::NAT_IDLE:
case UpnpNat::NAT_STAT::NAT_DISCOVERY:
case UpnpNat::NAT_STAT::NAT_GETDESCRIPTION:
case UpnpNat::NAT_STAT::NAT_DESCRIPTION_FOUND:
break;
case UpnpNat::NAT_STAT::NAT_FOUND:
nat.requestDescription();
break;
case UpnpNat::NAT_STAT::NAT_READY:
nat.addPortMapping("UpnpTest", nat.localIp(), 6664, 6664, "TCP");
break;
case UpnpNat::NAT_STAT::NAT_ADD:
qDebug() << "It worked!";
app.quit();
break;
case UpnpNat::NAT_STAT::NAT_ERROR:
qDebug() <<"Error:" <<nat.error();
app.exit(1);
break;
}
});
nat.discovery();
  1. We create the object (l:0)
  2. We connect to statusChanged signal to get notified (l:2)
  3. When status is NAT_FOUND, we request the description (l:11)
  4. When status is NAT_READY, we request the port mapping (l:14)
  5. When status is NAT_ADD, It means the port mapping request has been added, It worked! The application quits.(l:17)
  6. When status is NAT_ERROR, Error occured and display the error text. The application exits on error. (l:21)
  7. We connect to error changed in order to detect errors. (l:14)
  8. We start the discovery. (l:28)

Technical explainations

The discovery

Basically, we need to know if there is a upnp server around. To do so, we send an M-SEARCH request on the multicast address.

Here is the code:

#define HTTPMU_HOST_ADDRESS "239.255.255.250"
#define HTTPMU_HOST_PORT 1900
#define SEARCH_REQUEST_STRING "M-SEARCH * HTTP/1.1\n" \
"ST:UPnP:rootdevice\n" \
"MX: 3\n" \
"Man:\"ssdp:discover\"\n" \
"HOST: 239.255.255.250:1900\n" \
"\n"
void UpnpNat::discovery()
{
setStatus(NAT_STAT::NAT_DISCOVERY);
m_udpSocketV4.reset(new QUdpSocket(this));
QHostAddress broadcastIpV4(HTTPMU_HOST_ADDRESS);
m_udpSocketV4->bind(QHostAddress(QHostAddress::AnyIPv4), 0);
QByteArray datagram(SEARCH_REQUEST_STRING);
connect(m_udpSocketV4.get(), &QTcpSocket::readyRead, this, [this]() {
QByteArray datagram;
while(m_udpSocketV4->hasPendingDatagrams())
{
datagram.resize(int(m_udpSocketV4->pendingDatagramSize()));
m_udpSocketV4->readDatagram(datagram.data(), datagram.size());
}
QString result(datagram);
auto start= result.indexOf("http://");
if(start < 0)
{
setError(tr("Unable to read the beginning of server answer"));
setStatus(NAT_STAT::NAT_ERROR);
return;
}
auto end= result.indexOf("\r", start);
if(end < 0)
{
setError(tr("Unable to read the end of server answer"));
setStatus(NAT_STAT::NAT_ERROR);
return;
}
m_describeUrl= result.sliced(start, end - start);
setStatus(NAT_STAT::NAT_FOUND);
m_udpSocketV4->close();
});
connect(m_udpSocketV4.get(), &QUdpSocket::errorOccurred, this, [this](QAbstractSocket::SocketError) {
setError(m_udpSocketV4->errorString());
setStatus(NAT_STAT::NAT_ERROR);
});
m_udpSocketV4->writeDatagram(datagram, broadcastIpV4, HTTPMU_HOST_PORT);
}

The whole goal of the discovery is to get the description file from the server with all available devices and services. The result is stored in m_describeUrl.

Request Description file

Simple request using QNetworkAccessManager.

void UpnpNat::requestDescription()
{
setStatus(NAT_STAT::NAT_GETDESCRIPTION);
QNetworkRequest request;
request.setUrl(QUrl(m_describeUrl));
m_manager.get(request);
}

Parsing Description file

Your physical network device may act as several Upnp devices. You are looking for one of these device type:

Those type are followed with a number (1 or 2), It is the Upnp protocol version supported by the device.

void UpnpNat::processXML(QNetworkReply* reply)
{
auto data= reply->readAll();
if(data.isEmpty()) {
setError(tr("Description file is empty"));
setStatus(NAT_STAT::NAT_ERROR);
return;
}
setStatus(NAT_STAT::NAT_DESCRIPTION_FOUND);
/*
Boring XML&nbsp;parsing in order to find devices and services.
Devices:
constexpr auto deviceType1{"urn:schemas-upnp-org:device:InternetGatewayDevice"};
constexpr auto deviceType2{"urn:schemas-upnp-org:device:WANDevice"};
constexpr auto deviceType3{"urn:schemas-upnp-org:device:WANConnectionDevice"};
Services:
constexpr auto serviceTypeWanIP{"urn:schemas-upnp-org:service:WANIPConnection"};
constexpr auto serviceTypeWANPPP{"urn:schemas-upnp-org:service:WANPPPConnection"};
*/
m_controlUrl = /* Most important thing to find the controlUrl of the proper service.*/
setStatus(NAT_STAT::NAT_READY);
}

Send mapping Request

Sending a request is just sending HTTP request with the proper data.

I use inja to generate the http data properly.

This is the inja template.

<?xml version="1.0" encoding="utf-8"?>
<s:Envelope
xmlns:s="http://schemas.xmlsoap.org/soap/envelope/"
s:encodingStyle="http://schemas.xmlsoap.org/soap/encoding/">
<s:Body>
<u:AddPortMapping
xmlns:u="{{ service }}">
<NewRemoteHost></NewRemoteHost>
<NewExternalPort>{{ port }}</NewExternalPort>
<NewProtocol>{{ protocol }}</NewProtocol>
<NewInternalPort>{{ port }}</NewInternalPort>
<NewInternalClient>{{ ip }}</NewInternalClient>
<NewEnabled>1</NewEnabled>
<NewPortMappingDescription>{{ description }}</NewPortMappingDescription>
<NewLeaseDuration>0</NewLeaseDuration>
</u:AddPortMapping>
</s:Body>
</s:Envelope>

Then, let's create a json object with all data. As final step, we need to create a request, set its data, and then post it.

void UpnpNat::addPortMapping(const QString& description, const QString& destination_ip, unsigned short int port_ex,
unsigned short int port_in, const QString& protocol)
{
inja::json subdata;
subdata["description"]= description.toStdString();
subdata["protocol"]= protocol.toStdString();
subdata["service"]= m_serviceType.toStdString();
subdata["port"]= port_in;
subdata["ip"]= destination_ip.toStdString();
auto text= QByteArray::fromStdString(inja::render(loadFile(key::envelop).toStdString(), subdata));
QNetworkRequest request;
request.setUrl(QUrl(m_controlUrl));
QHttpHeaders headers;
headers.append(QHttpHeaders::WellKnownHeader::ContentType, "text/xml; charset=\"utf-8\"");
headers.append("SOAPAction", QString("\"%1#AddPortMapping\"").arg(m_serviceType));
request.setHeaders(headers);
m_manager.post(request, text);
}

Finally, just check the answer

The reply has no error, it worked, the status changes to NAT_ADD. Otherwise, the status changes to error.

void UpnpNat::processAnswer(QNetworkReply* reply)
{
if(reply->error() != QNetworkReply::NoError)
{
setError(tr("Something went wrong: %1").arg(reply->errorString()));
setStatus(NAT_STAT::NAT_ERROR);
return;
}
setStatus(NAT_STAT::NAT_ADD);
}

Don't hesitate to test it on your own device. Just to validate, it works everywhere. Any comment or change request, please use Github for that.

Source code

09 Jan 2026 5:38pm GMT

KDE Ships Frameworks 6.22.0

Friday, 9 January 2026

KDE today announces the release of KDE Frameworks 6.22.0.

This release is part of a series of planned monthly releases making improvements available to developers in a quick and predictable manner.

New in this version

Baloo
  • [FileWatchTest][KInotifyTest] Minor cleanup. Commit.
  • [FileWatchTest][KInotifyTest] Cover atomic file replacing. Commit.
  • [FileWatchTest] Cleanup and extend comments, correct wait condition. Commit.
Bluez Qt
  • Export header file for battery. Commit.
  • Remove duplicate headers in same file. Commit.
Breeze Icons
  • Add new icon set: view-visible-off. Commit.
  • Add kjournaldbrowser icon symlinks for flatpak app. Commit.
  • Optionally disable icons library. Commit.
  • Handle cross compiling for tools. Commit.
Extra CMake Modules
  • FindInotify: fix comparison. Commit.
  • ECMAddTests: add functions and variables to preset test name prefix. Commit.
KArchive
  • 7z: Fix infinite loop in broken files. Commit.
  • 7z: Fix infinite loop in malformed file. Commit.
  • 7z: Fix infinite loop in malformed file. Commit.
KCalendarCore
  • Make build qml plugin optional (default on). Commit.
KCMUtils
  • Handle buttons changing at runtime. Commit.
KCodecs
  • [KEncodingProber] Add UTF16 surrogate pair support to state table. Commit.
  • [KEncodingProber] Stop rejecting valid MSBs in UTF-16 state machine. Commit.
  • [KEncodingProber] Reduce model switching in HandleData(). Commit.
  • [KEncodingProber] Avoid passing mangled data to the UnicodeGroupProber. Commit.
  • [KEncodingProber] Untangle HebrewProber and SBCharSetProber. Commit.
  • [KEncodingProber] Minor cleanups for SBCharSetProber. Commit.
  • [KEncodingProber] Various minor cleanups for HebrewProber. Commit.
  • [KEncodingProber] Add test for Windows-1253/iso8859-7 CP (Greek). Commit.
  • [KEncodingProber] Add some more testing for windows-1252 codepage. Commit.
  • [KEncodingProber] Add tests for Windows-1255 CP and UTF-8 Hebrew text. Commit.
  • [KEncodingProber] Simplify and improve unit test check condition. Commit.
  • [KEncodingProber] Verify short inputs cause no crash. Commit.
  • [KEncodingProber] Fix UTF-16 BOM detection. Commit.
  • [KEncodingProber] Verify confidence in unit tests. Commit.
  • [KEncodingProber] Remove no longer used dedicated Japanese/Chinese probers. Commit.
  • [KEncodingProber][MBCS] Allow to use only a subset of probers. Commit.
  • [KEncodingProber][MBCS] Remove unused includes from header file. Commit.
KColorScheme
  • Use Qt API instead of KColorSchemeWatcher. Commit.
KConfig
  • Add std::chrono convenience helpers. Commit.
  • Add long and ulong as supported types. Commit.
  • KConfigIniBackend::parseConfig: reuse allocated buffer to read group name. Commit.
  • Use ECM_TEST_NAME_PREFIX. Commit.
  • Fix FindNext shortcut on macos. Commit.
KConfigWidgets
  • Fix scanning for kf6_entry.desktop files in "locale" root dirs & parents. Commit.
KContacts
  • Use ECM_TEST_NAME_PREFIX. Commit.
KCoreAddons
  • Add OpenBSD available memory and refactor. Commit.
  • Remove duplicate headers in same file. Commit.
KDav
  • Don't hide when the principals home sets fetch failed. Commit.
  • Use ECM_TEST_NAME_PREFIX. Commit.
KDeclarative
  • DeclarativeDragArea: Filter mouse events according to acceptedButtons(). Commit. Fixes bug #384009
KDocTools
KGuiAddons
KHolidays
  • Update Japanese holidays for 2024-2026. Commit.
  • Update holiday_cn_zh-cn for 2026 CN holidays. Commit.
  • Add comprehensive list of Nepalese holidays for 2026-2030 when possible. Commit.
KI18n
  • Docs: expand org.kde.ki18n coverage. Commit.
  • Remove duplicate headers in same file. Commit.
KIconThemes
  • KIconLoader: When processing SVG, skip whitespace and comments. Commit.
KImageformats
  • Add YCgCo-Re AVIF test. Commit.
  • Avif: YCgCo-Re decoding fix. Commit.
  • HEIF tests skipped using kde-ci.yml. Commit.
  • Add allocation limit test (0/256 MiB). Commit.
  • AllocationLimit = 0 means no limit. Commit.
KIO
  • Drop no longer needed moc include. Commit.
  • Add manual test for FavIconRequestJob. Commit.
  • KFilePlacesModel: Set desktop file name for partition manager. Commit.
  • Remove duplicate headers in same file. Commit.
  • Deprecate PreviewJob::removeItem(QUrl). Commit.
  • PreviewJob: fix empty enabledPlugins attribute set for thumbnail job. Commit.
  • PreviewItem struct: drop unused cacheSize member. Commit.
  • UDSEntryPrivate::load: reuse allocated buffer for string data. Commit.
  • UDSEntryPrivate::load: avoid repeated look-up in cachedStrings list. Commit.
  • Kfileplacesview: remove DropAction::MoveAction from the list of supportedActions. Commit. Fixes bug #509231
  • RenameDialog: Add "Compare Files" button. Commit.
  • Use ECM_TEST_NAME_PREFIX. Commit.
Kirigami
  • Controls/SwipeListItem: remove dead validate function. Commit.
  • Downgrade the message about not finding a plaform plugin to debug. Commit.
  • Controls/NavigationTabButton: remove Qt 6.8 check. Commit.
  • Layouts/FormLayout: avoid binding loop during initial load. Commit. Fixes bug #513185
  • Controls/GlobalDrawer: explicitly pass backItem. Commit.
  • Controls/private: pass refreshing to PullDownIndicator. Commit.
  • Autotests: linting changes. Commit.
  • Examples: linting. Commit.
  • Drop non-existing handler parameters. Commit.
  • Primitives/Separator: import primitives. Commit.
  • Tests: various linting fixes for the manual tests. Commit.
  • SwipeListItem: Use implicitContentHeight/Width instead of contentItem.implicitHeight/Width. Commit.
  • Avoid custom-parsed PropertyChanges. Commit.
  • Remove unused imports. Commit.
  • Port to ComponentBehavior Bound and required properties. Commit.
  • Qualify access to parent properties. Commit.
  • Controls/NavigationTabButton: fix pointSize binding. Commit.
  • Templates/AbstractApplicationHeader: always set preferredHeight. Commit.
  • Controls/Action: fix alternate shortcut. Commit.
  • Port to Application.styleHints. Commit.
  • Controls/AboutItem: qualify Separator. Commit.
  • SwipeListItem: Add back checking for parent width and implicitWidth. Commit.
  • Add extra margin only for default title. Commit.
  • Documentation fixes. Commit.
  • Document requirements for new components. Commit.
  • Add TitleSubtitleWithActions. Commit.
KItemModels
  • KDescendantsProxyModel: fix invalid call to index(-1,-1). Commit.
KJobWidgets
  • Remove duplicate headers in same file. Commit.
KPackage
KService
  • Remove FormFactor handling. Commit.
  • Drop Library handling. Commit.
  • Drop allowAsDefault handling. Commit.
KStatusNotifieritem
  • Tweak KStatusNotifierItem::setAssociatedWindow. Commit.
KSVG
  • Remove noisy debug on ImageSetPrivate::findInCache. Commit.
  • Remove duplicate headers in same file. Commit.
KTextEditor
  • Correction: fixes typo introduced in previous commit. Commit.
  • Fix various typos. Commit.
KUserFeedback
  • CI - Flatpak - Update Runtime to 6.10. Commit.
KWidgetsAddons
  • KPageView: Fix top aligned widget stretch. Commit.
  • Remove duplicate headers in same file. Commit.
  • KDateTable: Don't paint days in dark red if high-contrast is active. Commit.
  • KPageView: Use correct icon mode if high-contrast color scheme is in use. Commit.
  • Add helper for checking if high-contrast color scheme is in use. Commit.
KWindowSystem
  • Add KWaylandExtras::{setXdgToplevelTag,setXdgToplevelDescription}. Commit. See bug #512447
  • Use ECM_TEST_NAME_PREFIX. Commit.
KXMLGUI
  • Remove dead gestures code. Commit.
Solid
  • /etc/mtab not exists on OpenBSD. Commit.
  • Fix some missing overrides in win backend. Commit.
Sonnet
  • Use ECM_TEST_NAME_PREFIX. Commit.
Syntax Highlighting
  • Add support for Toit. Commit.
  • Systemd unit: update to systemd v259. Commit.
  • Add Mermaid syntax highlighting. Commit. Implements feature #494286
  • New feature: stacking several contexts at the same time. Commit.
  • Dot: add Graphviz as alternative name. Commit.
  • Add new QML keywords. Commit.

09 Jan 2026 12:00am GMT

08 Jan 2026

feedPlanet KDE | English

Events in December 2025

December was quite an eventful month for me, with over 4,000 km travelled by train. This was in part caused by the holidays and visiting family, but also by the KDE PIM sprint in Paris and the 39th Chaos Communication Congress.

KDE PIM sprint in Paris

From the 12th to the 14th of December, I was in Paris. It was actually my first time there for more than a day trip, so I arrived a day earlier to explore the city a bit. I went on a walk across the city with Tobias and Nicolas, and I took some photos.

 

The weekend was also very productive. We advanced our goal of making KMime a proper KDE Framework; made Message-IDs in emails more privacy-conscious; and discussed various important topics such as the retirement of the Kolab resource and the switch to SQLite as the default backend for Akonadi.

 

Huge thanks to enioka Haute Couture for having us in their office in Paris.

The sprint being in Paris also allowed me to afterward go visit my grandma, 350 km further south of Paris, so this was particularly convenient.

39th Chaos Communication Congress (39c3)

Another event I went to was 39c3, which is the third year in a row that I attended, and this year again we had an assembly as part of the Bits und Bäume umbrella, thanks to Joseph.

 

I love the vibe of this event. It's not very dry or only tech-focused, but also has a big artistic and political aspect to it. And while the number of attendees is very large, at the same time it's very chill and I don't feel overwhelmed, unlike at FOSDEM.

At the KDE assembly, we met a lot of interested users, some GNOME friends, and since a bunch of KDE devs were there, we managed to work on a few productive things, like switching the map backend from Itinerary to MapLibre.

And this year, I even managed to go on national TV for a few seconds to speak about Nextcloud. My German grandma called me the day afterward, very happy to have seen me.

 

08 Jan 2026 10:00pm GMT