What's new in QML Tooling in 6.11, part 3: context property support

The latest Qt release, Qt 6.11, is just around the corner. This short blog post series presents the new features that QML tooling brings in Qt 6.11. You can find part 1 on new qmlls features here, and part 2 on new qmllint warnings here.

In this blog post, we will start with a short recap on the context property and go through the new context property support in qmllint and QML Language Server.

Qt 6.11 Released!

The 6.11 release for Qt Framework is now available, with improved performance, newly supported techniques and capabilities on graphics, connectivity and languages, not to mention a whole new approach to asynchronous C++ coding. Take a closer look.

Face and Voice Recognition on MCUs: The Edge AI Coffee Machine Challenge

Imagine a scenario - it’s Monday and you just arrived at the office at 7:55 AM since you have a meeting at 8 AM. What kind of person schedules a meeting at 8 AM? Everything is a pain and a hardship. You can barely see, but your smart coffee machine is your friend. It knows who you are and what your favorite drink is, it understands you, but doesn’t judge. It can’t give you a hug, but it can quickly brew you a cup of warm and fresh cup of your preferred beverage, helping you survive that meeting and come back to the land of the living.

Hotspot v1.6.0 released

by Editor Team (KDAB)

Hotspot v1.6.0 released

Hotspot is a standalone GUI designed to provide a user-friendly interface for analyzing performance data. It takes a perf.data file, parses and evaluates its contents, and presents the results in a visually appealing and easily understandable manner. Hotspot’s goal is to offer a modern alternative to perf report, making performance analysis on Linux systems more intuitive and efficient.

Version 1.6.0 introduces powerful new features, improved workflows, and numerous bug fixes to make profiling even smoother.

ChangeLog for Hotspot v1.6.0

This release focuses on usability improvements and extended analysis capabilities. The most notable additions are:

  • Support for archived perf files (e.g. perf.data.zip)
  • Regex filtering in the flamegraph
  • Tracepoint support
  • Various bug fixes and stability improvements

Open Archived perf Files Directly

Hotspot can now open perf recordings stored inside archives such as perf.data.zip.

This simplifies sharing profiling results, storing CI artifacts, and working with compressed recordings - no manual extraction required.

Regex in the Flamegraph

The Flamegraph view now supports regular expression filtering.

This makes it much easier to:

  • Match complex symbol patterns
  • Focus on specific subsystems or namespaces
  • Quickly narrow down large profiling datasets

Especially for large C++ codebases, regex search significantly speeds up navigation.

Hotspot_V1.6.0_Regex_Example

Regex in the flamegraph

Tracepoint Support

Hotspot 1.6.0 introduces support for tracepoints captured via perf.

This enables analysis of event-based data in addition to traditional sampling, giving deeper insight into system and runtime behavior.

Hotspot_V1.6.0_Tracepoints_Example

Tracepoints

Bug Fixes and Improvements

As usual, this release also includes numerous smaller fixes, UI refinements, and internal cleanups to improve overall stability and user experience.

For a complete overview of all changes, see the full changelog on GitHub.

Happy profiling!

Videos

Hotspot - A GUI for perf report:

Hotspot Demo from Embedded World 2022:

The post Hotspot v1.6.0 released appeared first on KDAB.

Qt Creator 19 released

We are happy to announce the release of Qt Creator 19!

Release 19 for the Qt Creator IDE adds a minimap for text editors, easier configuration of remote devices, a basic MCP server, lightweight support for various project types for various languages, and many more improvements.

Qt World Summit 2026: Going Virtual

Qt World Summit 2026 is moving to a virtual format. After careful evaluation with key stakeholders, we've decided to transition the event from an in-person gathering in Germany to an online experience.

REST Better with the Support of OpenAPI in Qt 6

Some of you are following our works to improve connectivity of Qt-based apps. For example, in this blogpost we explained enhacements in the Qt's network stack for more efficient use of RESTful APIs starting with Qt 6.7. So, it might sound we are done with REST. Why bother about OpenAPI then? Well, while around 70% of all web services run on REST, around 20-30% of them use code generated from OpenAPI specification. How could Qt leave that out without helping our users to code less and create more? 

The new Qt 6 OpenAPI module will become available with Qt 6.11 as a Technology Preview. The module introduces the Qt 6 OpenAPI generator, which generates Qt HTTP clients using Qt Network RESTful APIs.

It is important to note here that an OpenAPI generator for Qt 5 has been originally developed by the OpenAPI community. We took it into Qt 6, refactored it, and extended it.

In this blog post, you will learn about the new OpenAPI generator in Qt 6 and see how the new module can be used to implement a simple, Qt-based ChatGPT client application using specification of its API provided in the OpenAPI format.

Automating Repetitive GUI Interactions in Embedded Development with Spix

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.

QFuture ❤️ C++ coroutines

Ever since C++20 introduced coroutine support, I was wondering how this could integrate with Qt. Apparently I wasn’t the only one: before long, QCoro popped up. A really cool library! But it doesn’t use the existing future and promise types in Qt; instead it introduces its own types and mechanisms to support coroutines. I kept wondering why no-one just made QFuture and QPromise compatible – it would certainly be a more lightweight wrapper then.

With a recent project at work being a gargantuan mess of QFuture::then() continuations (ever tried async looping constructs with continuations only? 🥴) I had enough of a reason to finally sit down and implement this myself. The result: https://gitlab.com/pumphaus/qawaitablefuture.

Example

#include <qawaitablefuture/qawaitablefuture.h>

QFuture<QByteArray> fetchUrl(const QUrl &url)
{
    QNetworkAccessManager nam;
    QNetworkRequest request(url);

    QNetworkReply *reply = nam.get(request);

    co_await QtFuture::connect(reply, &QNetworkReply::finished);
    reply->deleteLater();

    if (reply->error()) {
        throw std::runtime_error(reply->errorString().toStdString());
    }
    co_return reply->readAll();
}

It looks a lot like what you’d write with QCoro, but it all fits in a single header and uses native QFuture features to – for example – connect to a signal. It’s really just syntax sugar around QFuture::then(). Well, that, and a bit of effort to propagate cancellation and exceptions. Cancellation propagation works both ways: if you co_await a canceled QFuture, the “outer” QFuture of coroutine will be canceled as well. If you cancelChain() a suspended coroutine-backed QFuture, cancellation will be propagated into the currently awaited QFuture.

What’s especially neat: You can configure where your coroutine will be resumed with co_await continueOn(...). It supports the same arguments as QFuture::then(), so for example:

QFuture<void> SomeClass::someMember()
{
    co_await QAwaitableFuture::continueOn(this);
    co_await someLongRunningProcess();
    // Due to continueOn(this), if "this" is destroyed during someLongRunningProcess(),
    // the coroutine will be destroyed after the suspension point (-> outer QFuture will be canceled)
    // and you won't access a dangling reference here.
    co_return this->frobnicate();
}

QFuture<int> multithreadedProcess()
{
    co_await QAwaitableFuture::continueOn(QtFuture::Launch::Async);

    double result1 = co_await foo();
    // resumes on a free thread in the thread pool
    process(result1);

    double result2 = co_await bar(result1);
    // resumes on a free thread in the thread pool
    double result3 = transmogrify(result2);

    co_return co_await baz(result3);
}

See the docs for QFuture::then() for details.

Also, if you want to check the canceled flag or report progress, you can access the actual QPromise that’s backing the coroutine:

QFuture<int> heavyComputation()
{
    QPromise<int> &promise = co_await QAwaitableFuture::promise();
    promise.setProgressRange(0, 100);

    double result = 0;

    for (int i = 0; i < 100; ++i) {
        promise.setProgressValue(i);
        if (promise.isCanceled()) {
            co_return result;
        }
        frobnicationStep(&result, i);
    }
    co_return result;
} 

Outlook

I’m looking to upstream this. It’s too late for Qt 6.11 (already in feature freeze), but maybe 6.12? There have been some proposals for coroutine support on Qt’s Gerrit already, but none made it past the proof-of-concept stage. Hopefully this one will make it. Let’s see.

Otherwise, just use the single header from the qawaitablefuture repo. It an be included as a git submodule, or you just vendor the header as-is.

Happy hacking!

Caveat: GCC < 13

There was a nasty bug in GCC’s coroutine support: https://gcc.gnu.org/bugzilla/show_bug.cgi?id=101367 It affects all GCC versions before 13.0.0 and effectively prevents you from writing co_await foo([&] { ... }); – i.e. you cannot await an expression involving a temporary lambda. You can rewrite this out as auto f = foo([&] { ... }); co_await f; and it will work. But there’s no warning at compile time. As soon as the lambda with captures is a temporary expression inside the co_await, it will crash and burn at runtime. Fixed with GCC13+, but took me a while to figure out why things went haywire on Ubuntu 22.04 (defaults to GCC11).

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/jsonContent-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?

Debugging Qt WebAssembly - DWARF

 

One of the most tedious tasks a developer will do is debugging a nagging bug. It's worse when it's a web app, and even worse when its a webassembly web app.


The easiest way to debug Qt Webassembly is by configuring using the -g argument, or CMAKE_BUILD_TYPE=Debug . Emscripten embeds DWARF symbols in the wasm binaries. 

NOTE: Debugging wasm files with DWARF works only in the Chrome browser with the help of a browser extension. 

C/C++ DevTools Support (DWARF) browser extension. If you are using Safari or Firefox, or do not want to or cannot install a browser extension, you will need to generate source maps, which I will look at in my next blog post. 

DWARF debugging

You need to also enable DWARF in the browser developer tools settings, but you do not need symlinks to the source directories, as you would need to using source maps, as the binaries are embedded with the full directory path. Like magic!

 Emscripten embeds DWARF symbols into the binaries built with -g by default, so re-building Qt or your application in debug mode is all you need to do.

Qt builds debug libraries by default using the optimized argument -g2, which produces less debugging info, but results in faster link times. To preserve debug symbols you need to build Qt debug using the -g or -g3 argument. Both of these do the same thing.

Using DWARF debugger



Open Chome with the extention mentioned before installed, and open the console tools. Navigate to the Qt for WebAssembly web application you need to debug. Once it opens, it may take a few seconds for all the symbols and files to get parsed. If you are debugging into Qt, this will take quite a few seconds - just keep waiting. 
The javascript console will soon contain file source file paths and sources. You can find your file to debug, and set breakpoints. Just reload the page and once it hits a breakpoint, it will stop execution and highlight the current line in the source view.  It also will show variable names and values.


You can then step though your code as you would debugging a desktop application.

Saving and Restoring Application Settings with QSettings in PyQt6 — Learn how to use QSettings to remember user preferences, window sizes, and configuration options between sessions

Most desktop applications need to remember things between sessions. Maybe your user picked a dark theme, resized the window, or toggled a feature on or off. Without a way to save those choices, your app would forget everything the moment it closes. That's where QSettings comes in.

QSettings is a class provided by Qt (and available through PyQt6) that gives you a simple, cross-platform way to store and retrieve application settings. It handles all the platform-specific details for you — on Windows it uses the registry, on macOS it uses property list files, and on Linux it uses configuration files. You just read and write values, and Qt figures out the rest.

In this tutorial, we'll walk through everything you need to know to start using QSettings effectively in your PyQt6 applications.

Creating a QSettings Object

To use QSettings, you first need to create an instance. The most common way is to pass in your organization name and application name:

python
from PyQt6.QtCore import QSettings

settings = QSettings('MyCompany', 'MyApp')

These two strings — the organization name and the application name — are used by Qt to determine where your settings are stored. They act like a namespace, keeping your app's settings separate from every other application on the system.

You can check exactly where your settings file lives by printing the file path:

python
print(settings.fileName())

On Linux, this might print something like:

python
/home/username/.config/MyCompany/MyApp.conf

On Windows, it would point to a registry path, and on macOS, a .plist file. You don't need to worry about these differences — QSettings handles it for you.

Storing Values

Saving a setting is as simple as calling setValue() with a key and a value:

python
settings.setValue('theme', 'Dark')
settings.setValue('font_size', 14)
settings.setValue('show_toolbar', True)

The key is a string that you'll use later to retrieve the value. The value can be a string, integer, boolean, list, or other common Python types. QSettings will serialize it appropriately.

That's it — the value is saved. When you call setValue(), the data is written to persistent storage (the exact timing depends on the platform, but it happens automatically).

Reading Values Back

To read a setting, use value():

python
theme = settings.value('theme')
print(theme)  # 'Dark'

If the key doesn't exist (for example, the very first time your app runs), value() returns None by default. You can provide a default value as the second argument:

python
theme = settings.value('theme', 'Light')

Now if there's no theme key stored yet, you'll get 'Light' instead of None.

Handling Types

One thing that catches people off guard: QSettings stores everything as strings internally (at least when using INI-style backends on Linux). This means that when you read back a number or boolean, you might get a string instead of the type you expected.

To handle this, you can pass the type parameter:

python
font_size = settings.value('font_size', 14, type=int)
show_toolbar = settings.value('show_toolbar', True, type=bool)

By specifying type=int or type=bool, you ensure that the returned value is the correct Python type, regardless of how it was stored internally. This is especially important for booleans — without the type parameter, you might get the string 'true' instead of the boolean True.

Checking if a Setting Exists

Before reading a value, you might want to check whether it has been set at all. Use contains():

python
if settings.contains('theme'):
    theme = settings.value('theme')
    print(f'Found saved theme: {theme}')
else:
    print('No theme saved yet, using default')
    settings.setValue('theme', 'Light')
    theme = 'Light'

This pattern is useful when you want to distinguish between "the user explicitly set this value" and "this is just the default."

A Complete Example

Let's put this all together in a small PyQt6 application that remembers the window size and position, as well as a user-selected theme. When you close the app, it saves these settings. When you reopen it, everything is restored.

python
import sys
from PyQt6.QtWidgets import (
    QApplication, QMainWindow, QComboBox,
    QVBoxLayout, QWidget, QLabel
)
from PyQt6.QtCore import QSettings, QSize, QPoint


class MainWindow(QMainWindow):
    def __init__(self):
        super().__init__()
        self.settings = QSettings('MyCompany', 'MyApp')

        self.setWindowTitle("QSettings Demo")

        # Create a simple UI with a theme selector
        layout = QVBoxLayout()

        layout.addWidget(QLabel("Choose a theme:"))

        self.theme_combo = QComboBox()
        self.theme_combo.addItems(['Light', 'Dark', 'Blue'])
        layout.addWidget(self.theme_combo)

        container = QWidget()
        container.setLayout(layout)
        self.setCentralWidget(container)

        # Restore settings
        self.load_settings()

    def load_settings(self):
        # Restore window size and position
        size = self.settings.value('window_size', QSize(400, 300))
        position = self.settings.value('window_position', QPoint(100, 100))
        self.resize(size)
        self.move(position)

        # Restore theme selection
        theme = self.settings.value('theme', 'Light')
        index = self.theme_combo.findText(theme)
        if index >= 0:
            self.theme_combo.setCurrentIndex(index)

    def save_settings(self):
        self.settings.setValue('window_size', self.size())
        self.settings.setValue('window_position', self.pos())
        self.settings.setValue('theme', self.theme_combo.currentText())

    def closeEvent(self, event):
        self.save_settings()
        super().closeEvent(event)


app = QApplication(sys.argv)
window = MainWindow()
window.show()
app.exec()

Run this app, move or resize the window, select a different theme from the dropdown, then close the app. When you run it again, the window should appear in the same position and size, with the same theme selected.

Let's walk through what's happening:

  • In __init__, we create a QSettings object with our organization and app names.
  • load_settings() reads values from persistent storage and applies them to the window and widgets. Notice how we pass default values (QSize(400, 300), QPoint(100, 100), 'Light') so the app has sensible starting values on the very first run.
  • save_settings() writes the current window size, position, and theme to settings.
  • closeEvent() is a built-in Qt method that gets called when the window is about to close. We override it to save our settings right before that happens.

Removing Settings

If you need to delete a stored setting, use remove():

python
settings.remove('theme')

This removes the theme key entirely. After this, settings.contains('theme') would return False.

Listing All Keys

To see everything that's currently stored, use allKeys():

python
keys = settings.allKeys()
print(keys)  # ['theme', 'font_size', 'show_toolbar', ...]

This can be handy for debugging, or if you want to iterate over all settings to display them in a preferences dialog.

Organizing Settings with Groups

As your app grows, you might end up with a lot of settings. QSettings supports groups, which let you organize keys into sections — similar to folders:

python
settings.beginGroup('appearance')
settings.setValue('theme', 'Dark')
settings.setValue('font_size', 14)
settings.endGroup()

settings.beginGroup('network')
settings.setValue('timeout', 30)
settings.setValue('retry_count', 3)
settings.endGroup()

When you read them back, you use the same group:

python
settings.beginGroup('appearance')
theme = settings.value('theme', 'Light')
settings.endGroup()

Alternatively, you can use a / separator in the key name as a shorthand:

python
settings.setValue('appearance/theme', 'Dark')
theme = settings.value('appearance/theme', 'Light')

Both approaches produce the same result. The slash syntax is a bit more concise, while beginGroup()/endGroup() is cleaner when you're reading or writing several settings in the same group at once.

Using QSettings with setOrganizationName and setApplicationName

Instead of passing the organization and app names every time you create a QSettings object, you can set them once on the QApplication:

python
app = QApplication(sys.argv)
app.setOrganizationName('MyCompany')
app.setApplicationName('MyApp')

After this, you can create QSettings objects without any arguments:

python
settings = QSettings()

It will automatically use the organization and application names you set. This is especially convenient in larger applications where you create QSettings in multiple places — you only need to define the names once at startup.

Managing Many Settings with a Dictionary

If your application has a lot of settings, checking each one individually can get repetitive. A cleaner approach is to define your defaults in a dictionary and loop through them:

python
DEFAULTS = {
    'theme': 'Light',
    'font_size': 14,
    'show_toolbar': True,
    'language': 'English',
    'auto_save': True,
    'auto_save_interval': 5,
}

settings = QSettings('MyCompany', 'MyApp')

# Load settings with defaults
config = {}
for key, default in DEFAULTS.items():
    config[key] = settings.value(key, default, type=type(default))

print(config)

By using type=type(default), each value is automatically cast to the same type as its default. This keeps everything tidy and makes it easy to add new settings later — just add another entry to the dictionary.

Where Are Settings Stored?

If you're curious about where QSettings puts your data, or if you need to find the settings file for debugging, here's a quick summary:

Platform Storage Location
Windows Registry under HKEY_CURRENT_USER\Software\MyCompany\MyApp
macOS ~/Library/Preferences/com.mycompany.MyApp.plist
Linux ~/.config/MyCompany/MyApp.conf

You can always check the exact path using:

python
print(settings.fileName())

On Linux and macOS, the settings file is a plain text file that you can open and inspect directly, which is helpful for debugging.

Working with QStandardPaths

QSettings tells you where settings are stored, but sometimes you need to know about other standard locations — where to store cached data, application data, or downloaded files. Qt provides QStandardPaths for this:

python
from PyQt6.QtCore import QStandardPaths

# Where to store app configuration
config_path = QStandardPaths.writableLocation(QStandardPaths.AppConfigLocation)
print(f'Config: {config_path}')

# Where to store app data
data_path = QStandardPaths.writableLocation(QStandardPaths.AppDataLocation)
print(f'Data: {data_path}')

# Where to store cached files
cache_path = QStandardPaths.writableLocation(QStandardPaths.CacheLocation)
print(f'Cache: {cache_path}')

QStandardPaths is separate from QSettings, but they complement each other well. Use QSettings for simple key-value preferences, and QStandardPaths when you need to store actual files (databases, logs, downloaded content) in the right platform-appropriate location.

Summary

QSettings gives you a clean, cross-platform way to persist user preferences in your PyQt6 applications. Here's a quick recap of the essentials:

  • Create a QSettings object with your organization and app name.
  • Use setValue(key, value) to save a setting.
  • Use value(key, default, type=...) to read a setting, with a default fallback and explicit type.
  • Use contains(key) to check if a setting exists.
  • Use remove(key) to delete a setting.
  • Override closeEvent() on your main window to save settings when the app closes.
  • Organize related settings with groups using beginGroup()/endGroup() or / in key names.

Once you're comfortable with these basics, you'll find that QSettings quietly handles one of those essential-but-tedious parts of desktop app development — letting you focus on the interesting stuff.

For an in-depth guide to building Python GUIs with PyQt6 see my book, Create GUI Applications with Python & Qt6.