<?xml version="1.0" encoding="UTF-8"?><rss version="2.0" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/"><channel><title>KDAB</title><description>Global software consultancy specialising in Qt, C++, Rust, Slint and Embedded Linux. Consulting, development, training and open-source tools since 1999.</description><link>https://www.kdab.com/</link><language>en-gb</language><item><title>⬢ Distrobox: An In-depth Guide for Developers - Part 1</title><link>https://www.kdab.com/distrobox-an-in-depth-guide-for-developers-part-1/</link><guid isPermaLink="true">https://www.kdab.com/distrobox-an-in-depth-guide-for-developers-part-1/</guid><description>&lt;p data-block-key=&quot;mb64t&quot;&gt;Distrobox gives developers a fast, practical way to work across Linux distributions without the setup time and resource overhead of full virtual machines. This in-depth guide explains how Distrobox builds on OCI containers and tools such as Podman or Docker to create tightly integrated development environments—with access to your home directory, graphical sessions, devices, and host tooling. Learn why it is useful for reproducing distro-specific bugs, testing software on multiple platforms, accessing alternative package repositories, and combining a stable host OS with flexible, disposable userlands.&lt;/p&gt;</description><pubDate>Wed, 23 Sep 2026 05:48:00 GMT</pubDate><content:encoded>&lt;h1&gt;⬢ Distrobox: An In-depth Guide for Developers - Part 1&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;motivation-and-background&quot; data-block-key=&quot;jtpnf&quot;&gt;Motivation &amp;amp; Background&lt;/h2&gt;&lt;p data-block-key=&quot;d86d2&quot;&gt;I generally use Ubuntu LTS at work, mainly due to how widely used and supported it is. However, there are many situations where it&apos;s helpful to use a different distro on the job.&lt;/p&gt;&lt;p data-block-key=&quot;9vmrs&quot;&gt;Maybe I need to:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;3e73c&quot;&gt;test against an older version of Ubuntu&lt;/li&gt;&lt;li data-block-key=&quot;cqmmv&quot;&gt;reproduce a bug reported on another distro&lt;/li&gt;&lt;li data-block-key=&quot;19jov&quot;&gt;test something on several distros&lt;/li&gt;&lt;li data-block-key=&quot;66gqo&quot;&gt;replicate a very specific environment&lt;/li&gt;&lt;li data-block-key=&quot;nvd3&quot;&gt;develop with an init system I don&apos;t have&lt;/li&gt;&lt;li data-block-key=&quot;8cv1k&quot;&gt;acquire specific packages or versions from a different package repository&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;8f59j&quot;&gt;There are a few solutions to this problem.&lt;/p&gt;&lt;p data-block-key=&quot;qn3t&quot;&gt;We can use:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;2hohu&quot;&gt;a virtual machine (hardware virtualization)&lt;/li&gt;&lt;li data-block-key=&quot;7b12s&quot;&gt;containers (docker / podman / etc.) (os-level virtualization)&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;antaq&quot;&gt;Let&apos;s briefly consider each of these options.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;virtual-machines&quot; data-block-key=&quot;ppwfv&quot;&gt;Virtual Machines&lt;/h3&gt;&lt;p data-block-key=&quot;b4gai&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Hardware_virtualization&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Hardware virtualization&lt;/a&gt; is an easy and classic solution to this problem. This is the virtualization paradigm of virtual machines run by &lt;a href=&quot;https://en.wikipedia.org/wiki/Hypervisor&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;hypervisors &amp;amp; virtual machine monitors&lt;/a&gt; like &lt;a href=&quot;https://en.wikipedia.org/wiki/QEMU&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;qemu&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/VirtualBox&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;virtualbox&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/VMware_Workstation&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;VMware Workstation&lt;/a&gt;, and so forth. However, virtual machines come with some issues.&lt;/p&gt;&lt;p data-block-key=&quot;5535h&quot;&gt;Firstly, the setup time can be quite long - downloading an iso, configuring the VM settings, installing the OS, setting up your environment with your preferred editor or IDE, shell, window manager or DE, and populating the system with libraries, compilers, language servers, and other tools. Then there can be additional time required to do things like enable clipboard sharing, which may not even work all that well for some more obscure distros. After all this, the VM itself both on disk and at runtime is a bit heavy. The hypervisor emulates hardware resources, including the entire storage device, and the bootloader and kernel are virtualized. This takes up quite a large chunk of persistent storage and typically has a relatively high memory footprint, often hindering the performance of applications, especially considering there is overhead for each resource that is not passed through to the host hardware.&lt;/p&gt;&lt;p data-block-key=&quot;e8kvu&quot;&gt;It can be quite useful despite these shortcomings, but as the number of virtual environments grows, the more these issues are multiplied. Moreover, certain processes, like installing Qt from the online installer, are simply repeated over and over unnecessarily.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;containers&quot; data-block-key=&quot;03j8f&quot;&gt;Containers&lt;/h3&gt;&lt;p data-block-key=&quot;71bkj&quot;&gt;&lt;a href=&quot;https://en.wikipedia.org/wiki/Containerization_(computing)&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Containers&lt;/a&gt; are also a choice solution, as they circumvent these shortcomings. They virtualize at the OS userland (see &lt;a href=&quot;https://en.wikipedia.org/wiki/OS-level_virtualization&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;OS-level virtualization&lt;/a&gt;) or application level. One way to think about this is the existence of several distinct userspaces, including the file system, on the same running kernel. Using the host&apos;s kernel eliminates most resource overhead and makes bootloader virtualization irrelevant. Clipboard sharing works out of the box, as the container&apos;s standard I/O is routed through a host terminal session when run interactively.&lt;/p&gt;&lt;p data-block-key=&quot;d1a9k&quot;&gt;There are some key shortcomings here as well though: without further integration with the host machine, there is still a need to do significant setup to obtain tooling and libraries. Furthermore, it will not inherently have a graphical environment -- in order to open a graphical application, like a text editor, or perhaps a Qt project that you are intending to test in the first place, you will need to pass through sockets for X11 or Wayland from the host. It&apos;s quite powerful that the display doesn&apos;t need to be virtualized (and for instance a separate Xorg server doesn&apos;t need to run like in a virtual machine), but the setup for this can be cumbersome with many command line arguments. Containers are meant to be sandboxed from the host system, and share only explicitly specified resources from the host. However, this is again powerful -- we can mount parts of the host filesystem to pass through libraries, binaries, git configuration, ssh keys, project files, and so forth to share these resources between the host and any number of guests. The issue is again the tedium of setting these capabilities up for every container, both when installing and running.&lt;/p&gt;&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;distrobox-as-a-solution-to-shortcomings&quot; data-block-key=&quot;fs444&quot;&gt;Distrobox As A Solution To Shortcomings&lt;/h3&gt;&lt;p data-block-key=&quot;ecccj&quot;&gt;&lt;a href=&quot;https://distrobox.it/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Distrobox&lt;/a&gt; provides a solution to this. Due to the flexibility of containers, the aforementioned cumbersome tasks can be done automatically such that the workflow is massively simplified for the purposes of passing through graphical sessions, installing packages, and sharing parts of the host filesystem. Additional tooling provided by distrobox enables tighter integration between host and guest, like running host commands from within the guest without mounting, and exporting programs from the guest to host, so that they can be recognized by the host&apos;s application launcher for example. Distrobox automatically mounts a home directory from the host along with removable devices, journal, dbus, &lt;code&gt;/dev&lt;/code&gt;, and many others, which can all be further configured. It can be used with several &lt;a href=&quot;https://en.wikipedia.org/wiki/OS-level_virtualization#Implementations&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;container management backends&lt;/a&gt;, namely &lt;a href=&quot;https://en.wikipedia.org/wiki/Docker_(software)&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;docker&lt;/a&gt;, &lt;a href=&quot;https://en.wikipedia.org/wiki/Podman&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;podman&lt;/a&gt;, and &lt;a href=&quot;https://github.com/89luca89/lilipod&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;lilipod&lt;/a&gt; (a tool the authors of distrobox created).&lt;/p&gt;&lt;p data-block-key=&quot;8nnnb&quot;&gt;Let&apos;s see an example of how much simpler this is. We can use the &lt;code&gt;distrobox&lt;/code&gt; flag &lt;code&gt;--dry-run&lt;/code&gt; to see what command is actually run on the backend when we issue a &lt;code&gt;distrobox&lt;/code&gt; command on the host. Here I use &lt;code&gt;podman&lt;/code&gt; as my container manager.&lt;/p&gt;&lt;p data-block-key=&quot;cmvcl&quot;&gt;Doing so, we can see that the following distrobox command:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;distrobox create -n tempalpine -i alpine -H /home/sus/temphome --init&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;translates to this podman command:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;podman create
--hostname &amp;quot;tempalpine.sus&amp;quot;
--name &amp;quot;tempalpine&amp;quot;
--privileged
--security-opt label=disable
--security-opt apparmor=unconfined
--pids-limit=-1
--user root:root
--ipc host
--network host
--label &amp;quot;manager=distrobox&amp;quot;
--label &amp;quot;distrobox.unshare_groups=1&amp;quot;
--env &amp;quot;SHELL=bash&amp;quot;
--env &amp;quot;HOME=/home/sus&amp;quot;
--env &amp;quot;container=podman&amp;quot;
--env &amp;quot;TERMINFO_DIRS=/usr/share/terminfo:/run/host/usr/share/terminfo&amp;quot;
--volume /:/run/host:rslave
--volume /tmp:/tmp:rslave
--volume &amp;quot;/usr/bin/distrobox-init&amp;quot;:/usr/bin/entrypoint:ro
--volume &amp;quot;/usr/bin/distrobox-export&amp;quot;:/usr/bin/distrobox-export:ro
--volume &amp;quot;/usr/bin/distrobox-host-exec&amp;quot;:/usr/bin/distrobox-host-exec:ro
--volume &amp;quot;/home/sus&amp;quot;:&amp;quot;/home/sus&amp;quot;:rslave
--volume /dev:/dev:rslave
--volume /sys:/sys:rslave
--volume /dev/pts
--volume /dev/null:/dev/ptmx
--volume /var/log/journal
--env &amp;quot;HOME=/home/sus/temphome&amp;quot;
--env &amp;quot;DISTROBOX_HOST_HOME=/home/sus&amp;quot;
--volume &amp;quot;/home/sus/temphome:/home/sus/temphome:rslave&amp;quot;
--volume /etc/hosts:/etc/hosts:ro
--volume /etc/resolv.conf:/etc/resolv.conf:ro
--annotation run.oci.keep_original_groups=1
--ulimit host
--systemd=always
--userns keep-id

--entrypoint /usr/bin/entrypoint
alpine
--verbose
--name &amp;quot;sus&amp;quot;
--user 1000
--group 1000
--home &amp;quot;/home/sus/temphome&amp;quot;
--init &amp;quot;1&amp;quot;
--nvidia &amp;quot;0&amp;quot;
--pre-init-hooks &amp;quot;&amp;quot;
--additional-packages &amp;quot;&amp;quot;
-- &amp;#x27;&amp;#x27;&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;which you can see mounts additional distrobox binaries like &lt;code&gt;distrobox-init&lt;/code&gt;, &lt;code&gt;distrobox-export&lt;/code&gt;, and &lt;code&gt;distrobox-host-exec&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;eah96&quot;&gt;These tools themselves provide additional helpful functionality. &lt;code&gt;distrobox-init&lt;/code&gt;, for example, is generally run automatically when a new container is initialized. It mounts several more directories, sets up &lt;code&gt;sudo&lt;/code&gt; if needed, passes through xorg or wayland, creates a user and group for the user, and so on. This would all need to be done manually without that binary.&lt;/p&gt;&lt;p data-block-key=&quot;92hj2&quot;&gt;Thus, using distrobox saves a tremendous amount of time and effort, and wraps several reused arguments and additional commands to get such a container running quickly and without much thought.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;additional-background:-containers-and-oci&quot; data-block-key=&quot;q2tsi&quot;&gt;Additional Background: Containers &amp;amp; OCI&lt;/h3&gt;&lt;p data-block-key=&quot;6d979&quot;&gt;For a fantastic introduction to containers and their usefulness, see this whitepaper: &lt;a href=&quot;https://www.kdab.com/containers-in-embedded/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://www.kdab.com/containers-in-embedded/&lt;/a&gt;&lt;/p&gt;&lt;p data-block-key=&quot;dc0pc&quot;&gt;Further information can be found here: &lt;a href=&quot;https://www.kdab.com/the-developers-guide-to-containers/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://www.kdab.com/the-developers-guide-to-containers/&lt;/a&gt;&lt;/p&gt;&lt;p data-block-key=&quot;4u8v&quot;&gt;Some of the below may summarize, add to, or just repeat concepts in the above links.&lt;/p&gt;&lt;p data-block-key=&quot;a49kk&quot;&gt;Linux containerization is made possible by the &lt;a href=&quot;https://en.wikipedia.org/wiki/Cgroups&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;cgroups&lt;/a&gt; &amp;amp; &lt;a href=&quot;https://en.wikipedia.org/wiki/Linux_namespaces&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;namespaces&lt;/a&gt; kernel features.&lt;/p&gt;&lt;p data-block-key=&quot;2u5nt&quot;&gt;You have likely heard of, or used, docker before. Docker is the premier suite of tools for creating and deploying containers. Major contributors to the project include Microsoft, Cisco, Google, Huawei, IBM, and Red Hat.&lt;/p&gt;&lt;p data-block-key=&quot;ets47&quot;&gt;Podman is a similar system, developed by Red Hat, that can act as a drop-in replacement for docker, as its API is identical.&lt;/p&gt;&lt;p data-block-key=&quot;e6klo&quot;&gt;Some advantages of podman over docker include:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;7qir6&quot;&gt;lack of a daemon -- &lt;code&gt;dockerd&lt;/code&gt; can become a single point of failure for all containers&lt;/li&gt;&lt;li data-block-key=&quot;7q3mf&quot;&gt;rootless by default, while running rootless &lt;code&gt;dockerd&lt;/code&gt; and docker-managed containers requires additional configuration&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;dh8q5&quot;&gt;These tools are designed to be OCI-compliant. &lt;a href=&quot;https://opencontainers.org/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;OCI (Open Container Initiative)&lt;/a&gt; is a set of open standards for containers. The specifications provide a common container image format and runtime model that enables using container images across multiple container management tools, and distribution of containers in a standardized packaging format. Tools can thus be made compliant with the OCI spec, and therefore should support any OCI-compliant container images. This is powerful, as someone can distribute a container image that should then work as a regular container spawned by docker or podman, and also work with distrobox, and other tooling around containers.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;From &lt;a href=&quot;https://opencontainers.org/about/overview/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Open Containers Initiative&apos;s About Page&lt;/a&gt;:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;py7qe&quot;&gt;The OCI currently contains three specifications: the Runtime Specification (runtime-spec), the Image Specification (image-spec) and the Distribution Specification (distribution-spec). The Runtime Specification outlines how to run a “filesystem bundle” that is unpacked on disk. At a high-level an OCI implementation would download an OCI Image then unpack that image into an OCI Runtime filesystem bundle. At this point the OCI Runtime Bundle would be run by an OCI Runtime.&lt;br/&gt;This entire workflow should support the UX that users have come to expect from container engines like Docker and rkt: primarily, the ability to run an image with no additional arguments:&lt;br/&gt;docker run example.com/org/app:v1.0.0&lt;br/&gt;rkt run example.com/org/app,version=v1.0.0&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;bmq15&quot;&gt;OCI Images more or less consist of metadata in json files, and a number of image layers. The image layers are essentially tar archives containing changesets on one another back to a base filesystem, which is, at its most simple, just an empty directory. The unpacked archives can be thought of like a diff format for an entire filesystem, and the manifest describes how these diffs are applied on top of one another to result in a filesystem that is runnable.&lt;/p&gt;&lt;p data-block-key=&quot;b4ohe&quot;&gt;For more information, see the &lt;a href=&quot;https://specs.opencontainers.org/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;official specification pages&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;distrobox&amp;#x27;s-purpose-in-terms-of-these-background-concepts&quot; data-block-key=&quot;6y94u&quot;&gt;Distrobox&apos;s Purpose In Terms of These Background Concepts&lt;/h3&gt;&lt;p data-block-key=&quot;c5phc&quot;&gt;&lt;a href=&quot;https://distrobox.it/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Distrobox&apos;s website&lt;/a&gt; provides a very concise summary of the tool that ties together all the content above:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;py7qe&quot;&gt;The distrobox environment is based on an OCI image. This image is used to create a container that seamlessly integrates with the rest of the operating system by providing access to the user’s home directory, the Wayland and X11 sockets, networking, removable devices (like USB sticks), systemd journal, SSH agent, D-Bus, ulimits, /dev and the udev database, etc.&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;It also provides some compelling use cases:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;ul&gt;&lt;li data-block-key=&quot;py7qe&quot;&gt;Provide a mutable environment on an immutable OS, like &lt;a href=&quot;https://distrobox.it/compatibility.md#host-distros&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;ChromeOS, Endless OS, Fedora Silverblue, OpenSUSE Aeon/Kalpa, Vanilla OS&lt;/a&gt;, or &lt;a href=&quot;https://distrobox.it/posts/steamdeck_guide/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;SteamOS3&lt;/a&gt;&lt;/li&gt;&lt;li data-block-key=&quot;d1i0n&quot;&gt;Provide a locally privileged environment for sudoless setups (eg. company-provided laptops, security reasons, etc…)&lt;/li&gt;&lt;li data-block-key=&quot;fdi1p&quot;&gt;To mix and match a stable base system (eg. Debian Stable, Ubuntu LTS, RedHat) with a bleeding-edge environment for development or gaming (eg. Arch, OpenSUSE Tumbleweed, or Fedora with the latest Mesa)&lt;/li&gt;&lt;li data-block-key=&quot;39a0q&quot;&gt;Leverage a high abundance of curated distro images for &lt;code&gt;docker&lt;/code&gt;/&lt;code&gt;podman&lt;/code&gt; to manage multiple environments.&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;/p&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;Some notable goals and features mentioned on the site include:&lt;/p&gt;&lt;/div&gt;
&lt;h2&gt;&lt;/h2&gt;
&lt;p&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;py7qe&quot;&gt;This project aims to bring &lt;b&gt;any distro userland to any other distro&lt;/b&gt; supporting &lt;code&gt;podman&lt;/code&gt;, &lt;code&gt;docker&lt;/code&gt;, or &lt;code&gt;lilipod&lt;/code&gt;. It has been written in POSIX shell to be as portable as possible and it does not have problems with dependencies and &lt;code&gt;glibc&lt;/code&gt; version’s compatibility.&lt;br/&gt;Refer &lt;a href=&quot;https://distrobox.it/compatibility/#supported-container-managers&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;HERE&lt;/a&gt; for a list of supported container managers and minimum supported versions.&lt;br/&gt;It also aims to enter the container &lt;b&gt;as fast as possible&lt;/b&gt;, every millisecond adds up if you use the container as your default environment for your terminal.&lt;/p&gt;&lt;/div&gt;&lt;/p&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;intro-to-using-distrobox&quot; data-block-key=&quot;a0u1k&quot;&gt;Intro to Using Distrobox&lt;/h2&gt;&lt;p data-block-key=&quot;fe851&quot;&gt;With the background out of the way, let&apos;s see how distrobox is used.&lt;/p&gt;&lt;p data-block-key=&quot;difrf&quot;&gt;Note:&lt;br/&gt;I&apos;m using &lt;a href=&quot;https://starship.rs/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;starship&lt;/a&gt; as my prompt, which will be relevant later. In my host environment, the prompt will look something like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ 
🎄&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;or&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~/exampledir 
🎄&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;instead of something like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ $&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;or&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~/exampledir $&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;You will notice that different containers often have different default prompts, and we will import my host&apos;s prompt into a container later in the article.&lt;/p&gt;&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;creating-containers&quot; data-block-key=&quot;w2oma&quot;&gt;Creating Containers&lt;/h2&gt;&lt;p data-block-key=&quot;5m3bn&quot;&gt;A basic command to create a distrobox container for &lt;a href=&quot;https://alpinelinux.org/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Alpine Linux&lt;/a&gt; looks like so:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ 
🎄 distrobox create --image alpine&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;&lt;code&gt;alpine&lt;/code&gt; is a shortname defined in &lt;code&gt;/etc/containers/registries.conf.d/shortnames.conf&lt;/code&gt;. on my machine, it corresponds to &lt;code&gt;docker.io/library/alpine&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;7hjk4&quot;&gt;We can also use &lt;code&gt;-i&lt;/code&gt; instead of &lt;code&gt;--image&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;51vk8&quot;&gt;The container will automatically be named &lt;code&gt;alpine&lt;/code&gt; here, automatically generated from the repository path &lt;code&gt;docker.io/library/alpine&lt;/code&gt;. Attaching a tag to the path also affects the name. For instance, &lt;code&gt;docker.io/library/alpine:latest&lt;/code&gt; or simply &lt;code&gt;alpine:latest&lt;/code&gt; would result in the name being &lt;code&gt;alpine-latest&lt;/code&gt;&lt;/p&gt;&lt;p data-block-key=&quot;cf2ft&quot;&gt;The container&apos;s &lt;b&gt;name&lt;/b&gt; is the way we interact with the container, to start, stop, enter, or delete it.&lt;/p&gt;&lt;p data-block-key=&quot;8fnsd&quot;&gt;Let&apos;s say we want a &lt;a href=&quot;https://www.gentoo.org/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;gentoo&lt;/a&gt; image. There is no &quot;gentoo&quot; shortname in &lt;code&gt;shortnames.conf&lt;/code&gt;, so we will provide the full path and tag:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ 
🎄 distrobox create -i docker.io/gentoo/stage3:latest&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;This works fine, but it names the container &lt;code&gt;stage3-latest&lt;/code&gt;. I might forget this name, so it&apos;s easier if i just call it &lt;code&gt;gentoo&lt;/code&gt;. We specify a name for it with &lt;code&gt;--name&lt;/code&gt; or &lt;code&gt;-n&lt;/code&gt;:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ 
🎄 distrobox create -i docker.io/gentoo/stage3:latest -n gentoo&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;&lt;a href=&quot;https://distrobox.it/compatibility/#containers-distros&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Here is a list&lt;/a&gt; of supported container images that are officially tested. Other images can work too, but these are tested explicitly by the distrobox developers.&lt;/p&gt;&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;entering-containers&quot; data-block-key=&quot;gyk5e&quot;&gt;Entering Containers&lt;/h2&gt;&lt;p data-block-key=&quot;enfui&quot;&gt;Let&apos;s actually use a container and show what it looks like and how it can be useful.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ 
🎄 distrobox enter alpine
Starting container...                   	 [ OK ]
Installing basic packages...            	 [ OK ]
Setting up devpts mounts...             	 [ OK ]
Setting up read-only mounts...          	 [ OK ]
Setting up read-write mounts...         	 [ OK ]
Setting up host&amp;#x27;s sockets integration...	 [ OK ]
Integrating host&amp;#x27;s themes, icons, fonts...	 [ OK ]
Setting up package manager exceptions...	 [ OK ]
Setting up distrobox profile...         	 [ OK ]
Setting up sudo...                      	 [ OK ]
Setting up user groups...               	 [ OK ]
Setting up kerberos integration...      	 [ OK ]
Setting up user&amp;#x27;s group list...         	 [ OK ]
Setting up existing user...             	 [ OK ]
Setting up user home...                 	 [ OK ]
Ensuring user&amp;#x27;s access...               	 [ OK ]

Container Setup Complete!
sus@alpine:~$&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;If we look at &lt;code&gt;/lib&lt;/code&gt; in the container, we get our alpine image&apos;s &lt;code&gt;lib&lt;/code&gt; files:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;sus@alpine:~$ ls /lib
apk       ld-linux-x86-64.so.2  libc.musl-x86_64.so.1  libcrypt.so.1    libm.so.6        libresolv.so.2  libutil.so.1  modules-load.d
firmware  ld-musl-x86_64.so.1   libc.so.6              libgcompat.so.0  libpthread.so.0  librt.so.1      mdev          sysctl.d&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;This should be different from the &lt;code&gt;/lib&lt;/code&gt; directory on my host machine and feel like its own machine, almost like a VM. But, distrobox also mounted my host&apos;s &lt;code&gt;$HOME&lt;/code&gt; as a read-write directory. If we &lt;code&gt;ls&lt;/code&gt; in &lt;code&gt;~&lt;/code&gt; we will see the files from our host&apos;s &lt;code&gt;~&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;4s4ka&quot;&gt;We can use &lt;a href=&quot;https://wiki.alpinelinux.org/wiki/Alpine_Package_Keeper&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;alpine&apos;s package manager&lt;/a&gt; to get programs we want inside the container:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;sus@alpine:~$ vim
bash: vim: command not found
sus@alpine:~$ sudo apk add vim
(1/4) Installing vim-common (9.1.1012-r0)
(2/4) Installing xxd (9.1.1012-r0)
(3/4) Installing vim (9.1.1012-r0)
(4/4) Installing vim-doc (9.1.1012-r0)
Executing busybox-1.37.0-r9.trigger
OK: 546 MiB in 367 packages
sus@alpine:~$ vim&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;If we don&apos;t want it to specifically be an alpine package, and we already have the program on the host, we can also mount the host&apos;s installation files when we create the container. This grants us the flexibility to use what we already have where we can, and use the container distribution to get other packages.&lt;/p&gt;&lt;p data-block-key=&quot;c7ndp&quot;&gt;Our installed programs also persist when we restart the container.&lt;/p&gt;&lt;p data-block-key=&quot;6jc03&quot;&gt;If we install something in a running container that we don&apos;t have on the host system:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;sus@alpine:~$ sudo apk add qutebrowser
...
...
sus@alpine:~$ qutebrowser&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;This will open &lt;a href=&quot;https://qutebrowser.org/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;qutebrowser&lt;/a&gt;, which is a graphical application, using the host&apos;s X session (assuming you are running an Xorg session).&lt;/p&gt;&lt;p data-block-key=&quot;1a9hc&quot;&gt;If we leave the container (Ctrl+D, exit, logout, etc.) and try to use qutebrowser:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;sus@alpine:~$ 
logout

~ took 13m1s 
🎄 qutebrowser
Command &amp;#x27;qutebrowser&amp;#x27; not found, but can be installed with:
sudo apt install qutebrowser

~ 
🎄&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;... we clearly can&apos;t use it, as the container has the program, not our host system. However, we can run it from the host system without entering an interactive container session by specifying a single command to run in the container, like so:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp &quot;&gt;~ 
🎄 distrobox enter alpine -- qutebrowser&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;11crm&quot;&gt;We can also export it from the container so we can invoke the command in distrobox from the host, and even make the program locatable by our application launcher.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;next-steps&quot; data-block-key=&quot;2rnff&quot;&gt;Next Steps&lt;/h2&gt;&lt;p data-block-key=&quot;ebb9h&quot;&gt;In the &lt;a href=&quot;https://www.kdab.com/distrobox-an-in-depth-guide-for-developers-part-2/&quot;&gt;next part of the series&lt;/a&gt;, we&apos;ll explore some more involved examples, including:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;8vfge&quot;&gt;using additional flags to customize how our container is created and integrated with the host&lt;/li&gt;&lt;li data-block-key=&quot;1155m&quot;&gt;further integrating the host with the guest&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/distrobox-an-in-depth-guide-for-developers-part-1/&quot;&gt;⬢ Distrobox: An In-depth Guide for Developers - Part 1&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded></item><item><title>Using AI to modernize or migrate code</title><link>https://www.kdab.com/using-ai-to-modernize-or-migrate-code/</link><guid isPermaLink="true">https://www.kdab.com/using-ai-to-modernize-or-migrate-code/</guid><description>&lt;p data-block-key=&quot;mmufq&quot;&gt;AI agents are highly effective at accelerating code modernization and migration, but one-click transformations remain a risky promise. Drawing on decades of migration experience, KDAB combines AI-assisted workflows with small, reviewable changes, rigorous testing, reusable agent skills, and a phased delivery model. The result is not merely transformed code, but software your team can understand, maintain, and trust.&lt;/p&gt;</description><pubDate>Wed, 02 Sep 2026 08:50:19 GMT</pubDate><content:encoded>&lt;h1&gt;Using AI to modernize or migrate code&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;mjopn&quot;&gt;Let’s be honest, agentic engineering is the most disruptive evolution I’ve experienced in the field of software development (to be fair, I wasn’t there when we moved from cards to assembler).&lt;/p&gt;&lt;p data-block-key=&quot;bmni3&quot;&gt;AI agents are now able to do many things fast at good (to very good) quality. They are particularly good when it comes to code transformation: be it a code modernization or code migration.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;code-transformation-the-old-way&quot; data-block-key=&quot;7tj3f&quot;&gt;Code transformation, the old way&lt;/h2&gt;&lt;p data-block-key=&quot;5eo0h&quot;&gt;KDAB engineers have completed many migrations over the last three decades: from Qt3 to Qt4 to Qt5 to Qt6, from MFC to Qt, from Motif to Qt… the workflow is always the same:&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;ol&gt;&lt;li data-block-key=&quot;4pv8l&quot;&gt;&lt;b&gt;Find a piece of code you want to transform (a class, a dialog, a function),&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;9f4um&quot;&gt;&lt;b&gt;Transform the code to fit your target (be it modernization or migration),&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;9aj18&quot;&gt;&lt;b&gt;Test,&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;cjitf&quot;&gt;&lt;b&gt;Peer-review.&lt;/b&gt;&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;e51b0&quot;&gt;At this point, the transformation is done. In order to apply the same transformation for the rest of the code, we try to automate it as much as we can. This means finding the &lt;i&gt;recipe&lt;/i&gt; for the transformation and writing a &lt;i&gt;script&lt;/i&gt; to complete it for yourself. I’m using script in a broad sense, it could be a simple Perl script, or a complete software aimed at transforming code (like KDAB’s migration tool &lt;a href=&quot;https://github.com/KDAB/knut)&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Knut&lt;/a&gt;).&lt;/p&gt;&lt;p data-block-key=&quot;5tujr&quot;&gt;At this point, you can run the script on other parts of the code to migrate the rest of the software. Still, you need to follow these steps:&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;ol&gt;&lt;li data-block-key=&quot;qbno&quot;&gt;&lt;b&gt;Apply the script to the code,&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;ariqb&quot;&gt;&lt;b&gt;Finish the transformation if needed&lt;/b&gt; (sometimes, the script can only do 80%, as the last 20% are too difficult to automate),&lt;/li&gt;&lt;li data-block-key=&quot;7irp7&quot;&gt;&lt;b&gt;Review,&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;f7ocs&quot;&gt;&lt;b&gt;Test,&lt;/b&gt;&lt;/li&gt;&lt;li data-block-key=&quot;bldgj&quot;&gt;&lt;b&gt;Peer-review&lt;/b&gt;.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;7d66a&quot;&gt;Never apply a transformation globally on the whole code, as it will make the last 3 steps almost impossible! Also, it’s important to note that there is always an infinity of variations of the code, meaning the script will most likely hit a corner case at some point or another, requiring manual help.&lt;/p&gt;&lt;p data-block-key=&quot;5los8&quot;&gt;Any migration like this is time-consuming. Even if automation reduces the time to market, there are many manual steps required to ensure there are no regressions and the software is still working as expected.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;code-transformation-the-ai-way&quot; data-block-key=&quot;xkq8k&quot;&gt;Code transformation, the AI way&lt;/h2&gt;&lt;p data-block-key=&quot;3631a&quot;&gt;AI agents have completely changed the way developers are now working, especially as they are very good with code transformation. And we have seen many small or large codebases being migrated with the help of agents (for example &lt;a href=&quot;https://bun.com/blog/bun-in-rust?utm_source=chatgpt.com&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;bun&lt;/a&gt;, &lt;a href=&quot;https://ladybird.org/posts/adopting-rust/?utm_source=chatgpt.com&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;ladybird&lt;/a&gt;, &lt;a href=&quot;https://bughunters.google.com/blog/scaling-memory-safety&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;google&lt;/a&gt; or even &lt;a href=&quot;https://claude.com/blog/ai-code-migration&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;anthropic&lt;/a&gt; itself).&lt;/p&gt;&lt;p data-block-key=&quot;ppe5&quot;&gt;This leads us to think that with an infinite amount of tokens, one can basically migrate anything. There are now many services proposing AI code transformation with the premise that you just need to swipe your credit card (I will only link &lt;a href=&quot;https://malus.sh/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;MALUS&lt;/a&gt;, humoristic one). Such one-click migration services are selling you a fantasy, a disaster waiting to happen.&lt;/p&gt;&lt;p data-block-key=&quot;7i8oj&quot;&gt;As much as I find interesting reading those successful migrations, I’m more and more dubious that such migration could be done with a click on a button, based on our collective migration experience in KDAB.&lt;/p&gt;&lt;p data-block-key=&quot;c8ua9&quot;&gt;Looking at the different steps done manually, you still need at least to &lt;b&gt;review&lt;/b&gt; and &lt;b&gt;test&lt;/b&gt; the software yourself. And even having 100% code coverage does not prevent from the agent making mistakes (read the excellent article on rewriting git to rust &lt;a href=&quot;https://blog.gitbutler.com/true-grit#agents-love-to-cheat&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt;).&lt;/p&gt;&lt;p data-block-key=&quot;fjfrk&quot;&gt;Every migration project is unique, and it’s important to read the report of those AI-assisted migrations in detail, and why it worked. To my knowledge, none ended up with just one click on a button ;)&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;code-transformation-the-kdab-way&quot; data-block-key=&quot;yc78n&quot;&gt;Code transformation, the KDAB way&lt;/h2&gt;&lt;p data-block-key=&quot;92ufc&quot;&gt;The KDAB team has used AI agents on different projects requiring code transformation, which accelerates the development greatly.&lt;/p&gt;&lt;p data-block-key=&quot;dl28b&quot;&gt;And while all those projects were executed by different persons, the conclusions are always roughly the same.&lt;/p&gt;&lt;p data-block-key=&quot;cim2c&quot;&gt;&lt;b&gt;Do not do any global transformation&lt;/b&gt;, but keep the changes small and local as much as possible. This allows the code to be reviewed by a human (as well as AI - nowadays almost a given), to do test focused on what’s changed and to correct and enhance the migration skills.&lt;/p&gt;&lt;p data-block-key=&quot;e4h42&quot;&gt;&lt;b&gt;Write agent’s skills&lt;/b&gt;. As every migration is unique, you can easily create your own skills to share in the team and reuse during the migration. This replaces writing a “script” for automating transformation.&lt;/p&gt;&lt;p data-block-key=&quot;3q9nh&quot;&gt;&lt;b&gt;Test, test, test&lt;/b&gt;. Nothing will replace human testing, to ensure that all features are working as expected. You don’t want to release a new version of your software that has not been field tested beforehand.&lt;/p&gt;&lt;p data-block-key=&quot;fvl0s&quot;&gt;In the end, all changes are still going through the CI, reviewed and tested by a human. AI definitely changed our approach to code transformation and improved our velocity. Still, the quality of the work we are delivering needs to be ensured.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;transformation-strategy&quot; data-block-key=&quot;gxth4&quot;&gt;Transformation Strategy&lt;/h3&gt;&lt;p data-block-key=&quot;9hqm8&quot;&gt;One helpful approach is to split the work into &lt;i&gt;three phases:&lt;/i&gt;&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;ol&gt;&lt;li data-block-key=&quot;bu6m0&quot;&gt;&lt;b&gt;Stabilization phase:&lt;/b&gt; before touching the actual migration, use AI to harden the existing codebase. Expand test coverage, prune dead code, and normalize formatting. This is not just cosmetic changes, it will reduce work, conflicts and regressions later on.&lt;/li&gt;&lt;li data-block-key=&quot;8uufl&quot;&gt;&lt;b&gt;Pilot phase:&lt;/b&gt; the code transformation is done on a subset of the code. This allows to establish the architectural patterns, tooling and the general workflow for the migration.&lt;/li&gt;&lt;li data-block-key=&quot;2j95p&quot;&gt;&lt;b&gt;Completion phase:&lt;/b&gt; once the foundation is established, the migration continues. The proven approach from phase 1 is applied to the rest of the code instead of trial-and-error.&lt;/li&gt;&lt;/ol&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;bj32&quot;&gt;This approach is preferred by KDAB, as we can set solid foundations during phase one based on our expertise and experience, and gradually let the customer take over the work during the completion phase.&lt;/p&gt;&lt;p data-block-key=&quot;9u93i&quot;&gt;Over 25 years of migrations, we’ve learned that codebases are more than just code: it’s a succession of (sometimes bad) decisions based on context, context that AI agents often lack, that could have ended up with convoluted code or architecture issues. KDAB’s job during the pilot phase is to identify those before blindly copying them in the new code. This allows detecting architectural drift and broken assumptions.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h3 id=&quot;the-people-factor&quot; data-block-key=&quot;tdufg&quot;&gt;The people factor&lt;/h3&gt;&lt;p data-block-key=&quot;7i4b&quot;&gt;We must address another important factor during a code transformation: &lt;b&gt;ownership&lt;/b&gt;. AI agents can transform code at the speed of light, but they cannot mentor a team or build a consensus. A migration is only a success if the people who are going to inherit it understand it, trust it and maintain it.&lt;/p&gt;&lt;p data-block-key=&quot;b6g0l&quot;&gt;This is why the completion phase is also so important, as it is not just a handover of the code, but a transition period of knowledge transfer, a time for KDAB’s engineers to explain the migration process, the decisions made and the new architecture if needed.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;ai-accelerates-modernization-expertise-makes-it-last&quot; data-block-key=&quot;bbuvk&quot;&gt;AI accelerates modernization, expertise makes it last&lt;/h2&gt;&lt;p data-block-key=&quot;236g3&quot;&gt;As of fall 2026, you should treat an AI as a highly productive, hyper-capable junior developer with a very short attention span. It is engineered for speed, but it requires consistent, expert supervision.&lt;/p&gt;&lt;p data-block-key=&quot;8h0v0&quot;&gt;When it comes to code transformation, there is no &quot;magic button.&quot; A successful project requires more than just raw power; it requires a clear roadmap and experience to avoid ‘been there, done that’ traps, as well as a buy-in from the team maintaining the code.&lt;/p&gt;&lt;p data-block-key=&quot;ef0ee&quot;&gt;Starting with a rigorous pilot phase helps you validate your assumptions, test the agent’s performance against your specific architectural constraints, and determine whether the migration is truly worth the effort.&lt;/p&gt;&lt;p data-block-key=&quot;96v5n&quot;&gt;Ultimately, while AI agents have fundamentally shifted our approach to modernization, drastically increasing our velocity, they have not replaced the need for deep technical expertise. The value we deliver with the KDAB approach lies in ensuring that the final output is not just &quot;transformed,&quot; but stable, maintainable, and built on a foundation that will last for the next decade.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/using-ai-to-modernize-or-migrate-code/&quot;&gt;Using AI to modernize or migrate code&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Nicolas Arnaud-Cormos</dc:creator><category>ai</category><category>migration</category><category>modernization</category></item><item><title>OLE-Dispatch When Doing MFC/Qt Migration</title><link>https://www.kdab.com/ole-dispatch-when-doing-mfcqt-migration/</link><guid isPermaLink="true">https://www.kdab.com/ole-dispatch-when-doing-mfcqt-migration/</guid><description>&lt;p data-block-key=&quot;fik0n&quot;&gt;When porting MFC applications to Qt it might happen that you have to replicate dynamically created OLE controls and how they&apos;re controlled from the non-ported part of the application. This article presents a solution to connect Qt&apos;s Meta Object System into MFC/OLEs IDispatch interface.&lt;/p&gt;</description><pubDate>Thu, 20 Aug 2026 08:32:00 GMT</pubDate><content:encoded>&lt;h1&gt;OLE-Dispatch When Doing MFC/Qt Migration&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;arffy&quot;&gt;Most people who have ever ported an application from MFC to Qt know that at some point they have to integrate a QWidget into an MFC part of the user interface. Or the other way around, but that&apos;s a different story. Normally you would simply use a &lt;code&gt;QWinWidget&lt;/code&gt; for that to do exactly what you want. But what happens if your application created the old MFC window as an OLE Control Extension (OCX)? In that case the easiest solution is to intercept the control name and provide it to your own factory providing the correct widget, which can then be embedded in a &lt;code&gt;QWinWidget&lt;/code&gt;. Or you could use plugins to stick to the idea of it being DLLs. All roads lead to Rome. But what if your OLE based application uses the OLE-Dispatch way of initializing the controls? Setting properties, invoking methods, and so on?&lt;/p&gt;&lt;p data-block-key=&quot;428j&quot;&gt;I had exactly that problem and came up with a solution which glues OLE&apos;s &lt;code&gt;IDispatch&lt;/code&gt; interface together with Qt&apos;s &lt;code&gt;QMetaObject&lt;/code&gt;. That means you can access your &lt;code&gt;QWidget&lt;/code&gt;&apos;s (or any other &lt;code&gt;QObject&lt;/code&gt;) properties via the &lt;code&gt;IDispatch&lt;/code&gt; interface provided by my &lt;code&gt;QObjectDispatcher&lt;/code&gt; class. They only need to be declared via Qt&apos;s &lt;code&gt;Q_PROPERTY&lt;/code&gt; macro. The same applies to &lt;code&gt;slots&lt;/code&gt; and methods marked with &lt;code&gt;Q_INVOKABLE&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;4jecs&quot;&gt;Let&apos;s see how this is being used:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// This is the widget we&amp;#x27;re working with:
QWidget* someWidget = ...;

// Lets&amp;#x27; create our dispatcher:
// You have to delete it yourself or let COM do it for you via IUnknown::AddRef/Release
QObjectDispatcher* dispatcher = new QObjectDispatcher(someWidget);

// The code until this point is the only part which is new in your application, as
// far as dispatching the calls/properties is involved. From here, it&amp;#x27;s exactly the same;
// from the OLE MFC application&amp;#x27;s perspective we often only have an IUnknown:
IUnknown* pUnknown = dispatcher;

// Now get the interface and start playing with it:
CComQIPtr&amp;lt;IDispatch&amp;gt; pDispatch(pUnknown);

// First we have to retrieve the index of the property we change
LPOLESTR name = &amp;quot;windowTitle&amp;quot;;
DISPID dispId = -1;
pDispatch-&amp;gt;GetIDsOfNames(IID_NULL, &amp;amp;name, 1, LOCALE_SYSTEM_DEFAULT, &amp;amp;dispId);

// Now that we have the index, we change the property
VARIANT varTitle = CComVariant(&amp;quot;New window title&amp;quot;);
DISPSPARAMS dispParams = { nullptr, nullptr, 0, 0 };
dispParams.rgvarg = &amp;amp;varTitle;
dispParams.cArgs = 1;
pDispatch-&amp;gt;Invoke(dispId, IID_NULL, LOCALE_SYSTEM_DEFAULT, DISPATCH_PROPERTYPUT,
    &amp;amp;dispParams, nullptr, nullptr, nullptr);&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;arffy&quot;&gt;As you can see the API of &lt;code&gt;IDispatch&lt;/code&gt; is not fun to work with and there&apos;s no reason to use this except of making that kind of stuff work in existing MFC/OLE-applications while porting. I strongly advise against using it in new code.&lt;/p&gt;&lt;p data-block-key=&quot;bc8aq&quot;&gt;How does that work? Well, the &lt;code&gt;IDispatch&lt;/code&gt; interface works in two steps. First, you have to request the ID of the method/property you want to access via &lt;code&gt;GetIDsOfNames&lt;/code&gt;. You can request several IDs at the same time. &lt;code&gt;QObjectDispatcher&lt;/code&gt; traverses the methods of the handled &lt;code&gt;QObject&lt;/code&gt; via the corresponding &lt;code&gt;QMetaObject&lt;/code&gt;. It then returns the index of the method. If there’s no method found, it checks for a property. If it’s found, it returns the index of the property plus the number of methods, to be able to recognize the type afterwards. If neither a method nor a property by that name is found, an error is reported.&lt;/p&gt;&lt;p data-block-key=&quot;ecp0l&quot;&gt;Let’s have a short look at how &lt;code&gt;GetIDsOfNames&lt;/code&gt; roughly works. That example code cannot be used directly but gives you a rough idea of the implementation:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;HRESULT QObjectDispatcher::GetIDsOfNames(REFIID riid, LPOLESTR* rgszNames, UINT cnames, LCID, DISPID* rgDispId)
{
    // GetIdsOfNames supports requesting several at the same time
    for (uint i = 0; i &amp;lt; cNames; ++i) {
        // note you cannot use QMetaObject::indexOfMethod directly, 
        // since it expects the fully qualified name
        auto indexOfMethod = []() { /* ... */ };
        const int methodIndex = indexOfMethod(rgszNames[i]);
        if (methodIndex != -1) {
          rgDispId[i] = methodIndex;
          continue;
        }
    }
    // now do the same for the properties...
    // [...]
    return S_OK;
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;arffy&quot;&gt;In the second step you can call &lt;code&gt;Invoke&lt;/code&gt; to either invoke a method or to access a property. For this you need to pass a &lt;code&gt;struct DISPSPARAMS&lt;/code&gt; which contains the arguments. &lt;code&gt;rgvarg&lt;/code&gt; is an array to several arguments which are stored in reverse order. &lt;code&gt;QObjectDispatcher&lt;/code&gt; will now pick the method or property (depending on the id passed and whether you passed &lt;code&gt;DISPATCH_PROPERTYPUT&lt;/code&gt;, &lt;code&gt;DISPATCH_PROPERTYGET&lt;/code&gt;, or &lt;code&gt;DISPATCH_METHOD&lt;/code&gt;) and call it. For this it has to translate the arguments. For method calling, they need to be translated to &lt;code&gt;QMetaMethodArgument&lt;/code&gt; or &lt;code&gt;QMetaMethodReturnArgument&lt;/code&gt; for the return value. For properties, we have to go through &lt;code&gt;QVariant&lt;/code&gt; to be able to read/write the property via Qt’s Meta Object System.&lt;/p&gt;&lt;p data-block-key=&quot;cvpok&quot;&gt;Let’s also have a look at how &lt;code&gt;Invoke&lt;/code&gt; works internally. Even here, this code piece is not complete and won’t work directly:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;HRESULT QObjectDispatcher::Invoke(DISPID dispIdMember, REFIID riid, LCID, WORD wFlags, DISPPARAMS* pDispParams, VARIANT* pVarResult, EXCEPINFO*, UINT*)
{
    if (wFlags == DISPATCH_METHOD) {
        const QMetaMethod method = metaObject-&amp;gt;method(dispIdMember);

        // translate return argument
        auto returnArgument = translateReturnArgument(method, pVarResult);

        // then call the method, translating all the arguments
        invokeMethod(method, m_object, returnArgument, translateArgument(0), ...);

        return S_OK;
    }

    // [...]
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;arffy&quot;&gt;So, in your application, which expects a &lt;code&gt;CWnd&lt;/code&gt; generated by some factory, you do the following:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;1888j&quot;&gt;Create a wrapper &lt;code&gt;CWnd&lt;/code&gt; which you actually return to the caller. This &lt;code&gt;CWnd&lt;/code&gt; will in some way provide access to an &lt;code&gt;IDispatch&lt;/code&gt; instance like it was doing before your port&lt;/li&gt;&lt;li data-block-key=&quot;8b8ha&quot;&gt;Create a &lt;code&gt;QWinWidget&lt;/code&gt; residing inside of the &lt;code&gt;CWnd&lt;/code&gt;.&lt;/li&gt;&lt;li data-block-key=&quot;ehq6t&quot;&gt;Put your ported&lt;code&gt;QWidget&lt;/code&gt; into the &lt;code&gt;QWinWidget&lt;/code&gt;&lt;/li&gt;&lt;li data-block-key=&quot;67a9c&quot;&gt;Create a &lt;code&gt;QObjectDispatcher&lt;/code&gt; working on your ported &lt;code&gt;QWidget&lt;/code&gt;&lt;/li&gt;&lt;li data-block-key=&quot;1j0n8&quot;&gt;Make your &lt;code&gt;CWnd&lt;/code&gt;return this &lt;code&gt;QObjectDispatcher&lt;/code&gt; as &lt;code&gt;IDispatch&lt;/code&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;dateo&quot;&gt;Now your application should threat your ported widget as it was never ported. Of course, you have to provide the same properties and methods as the system expects using Qt&apos;s Meta Object System (&lt;code&gt;Q_PROPERTY&lt;/code&gt;, &lt;code&gt;Q_INVOKABLE&lt;/code&gt;).&lt;/p&gt;&lt;p data-block-key=&quot;cjt78&quot;&gt;There are, of course, some limitations:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;5vq11&quot;&gt;You cannot overload names of methods as OLE doesn&apos;t allow that&lt;/li&gt;&lt;li data-block-key=&quot;9n01j&quot;&gt;Optional parameters are not supported, as &lt;code&gt;QMetaMethod&lt;/code&gt; doesn&apos;t reflect that (or I simply haven&apos;t figured out, who knows…)&lt;/li&gt;&lt;li data-block-key=&quot;8ha4u&quot;&gt;You have to provide a type-mapping for the argument types. The example project linked below provides only &lt;code&gt;IUnknown*&lt;/code&gt;, &lt;code&gt;int&lt;/code&gt;, and &lt;code&gt;QString&lt;/code&gt; as these are the most common ones and show how it works&lt;/li&gt;&lt;li data-block-key=&quot;4lo96&quot;&gt;The example solution is not thread-safe&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;fi31m&quot;&gt;Find the complete code as a little example project on KDAB&apos;s GitHub repository at: &lt;a href=&quot;https://github.com/KDABLabs/blogs-qt/tree/main/MFCMigration-OLE-Dispatch&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/KDABLabs/blogs-qt/tree/main/MFC-Migration-OLE-Dispatch&lt;/a&gt;&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/ole-dispatch-when-doing-mfcqt-migration/&quot;&gt;OLE-Dispatch When Doing MFC/Qt Migration&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Christoph Schleifenbaum</dc:creator><category>migration</category><category>qt</category><category>windows</category></item><item><title>µ-Boosting Your CMake Productivity with Environment Variables</title><link>https://www.kdab.com/µ-boosting-your-cmake-productivity-with-environment-variables/</link><guid isPermaLink="true">https://www.kdab.com/µ-boosting-your-cmake-productivity-with-environment-variables/</guid><description>&lt;p data-block-key=&quot;ijanw&quot;&gt;Save yourself repetitive typing: CMake can read configuration values straight from environment variables, letting you set your preferred generator, enable compile_commands.json, and more - once in your shell config, applied to every build folder from then on.&lt;/p&gt;</description><pubDate>Wed, 12 Aug 2026 09:26:01 GMT</pubDate><content:encoded>&lt;h1&gt;µ-Boosting Your CMake Productivity with Environment Variables&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;evca8&quot;&gt;If you are working with C++ code chances are that you are using CMake to build your code, and for good reason. CMake is a powerful tool that allows you to target a wide range of compilers and platforms with a single buildsystem. Several large open source projects like Qt and KDE use CMake for that reason.&lt;/p&gt;&lt;p data-block-key=&quot;7ve57&quot;&gt;However, CMake does have a reputation for not always being the most ergonomic. For example, some useful options aren&apos;t enabled by default and need to be manually passed when invoking CMake, either from the command-line or an IDE.&lt;/p&gt;&lt;p data-block-key=&quot;ae1i7&quot;&gt;For example, you might want to use the &lt;code&gt;Ninja&lt;/code&gt; generator over the default one (e.g. Unix Makefiles on Linux) for faster builds. Enabling that is easy enough by passing &lt;code&gt;-G Ninja&lt;/code&gt; on the command line. Remembering to do that every time a new build folder is created gets old fast though.&lt;/p&gt;&lt;p data-block-key=&quot;6375u&quot;&gt;Another thing you might want to always enable is the &lt;code&gt;CMAKE_EXPORT_COMPILE_COMMANDS&lt;/code&gt; option. This generates a &lt;code&gt;compile_commands.json&lt;/code&gt; file that is used by tools like &lt;code&gt;clang-tidy&lt;/code&gt; or the &lt;code&gt;clangd&lt;/code&gt; language server (used for integration with code editors like Kate and Visual Studio Code).&lt;/p&gt;&lt;p data-block-key=&quot;7sshg&quot;&gt;One way to deal with repetitive CMake options are &lt;a href=&quot;https://cmake.org/cmake/help/latest/manual/cmake-presets.7.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;CMake presets&lt;/a&gt;. CMake presets are per-project configuration files that define sets of options to be applied. With that you can, for example, define presets for local development, CI builds, and release deployments, each with different options applied.&lt;/p&gt;&lt;p data-block-key=&quot;ag3na&quot;&gt;While CMake presets are a great tool for many use cases they don&apos;t help as much for quickly creating new projects. Fortunately, there&apos;s a different way to save yourself some typing: CMake environment variables.&lt;/p&gt;&lt;p data-block-key=&quot;8qi42&quot;&gt;CMake accepts configuration values not only from the command-line, but also environment variables from the system environment. For example, the default CMake generator is read from the &lt;code&gt;CMAKE_GENERATOR&lt;/code&gt; environment variable. Likewise, you can enable &lt;code&gt;compile_commands.json&lt;/code&gt; generation by setting the &lt;code&gt;CMAKE_EXPORT_COMPILE_COMMANDS&lt;/code&gt; environment variable to &lt;code&gt;ON&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;ff6ug&quot;&gt;By putting this&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-bash  line-numbers &quot;&gt;export CMAKE_EXPORT_COMPILE_COMMANDS=ON
export CMAKE_GENERATOR=Ninja&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;evca8&quot;&gt;in your bashrc or equivalent you get those options enabled any time you create a new build folder, without specifying them manually every time. How handy is that?&lt;/p&gt;&lt;p data-block-key=&quot;8onbe&quot;&gt;This applies not only to &lt;code&gt;cmake&lt;/code&gt; but also other tools provided by the CMake project. If you&apos;re using CMake, chances are that you&apos;re also using &lt;code&gt;ctest&lt;/code&gt; to drive your test suite, and it, too, can be configured through environment variables. When invoking &lt;code&gt;ctest&lt;/code&gt; on the commandline it will not show any of the tests’ output by default, making it hard to spot why a given test is failing. For this reason &lt;code&gt;ctest&lt;/code&gt; accepts the &lt;code&gt;--output-on-failure&lt;/code&gt; argument, which shows the test output for any failing tests. By setting &lt;code&gt;CTEST_OUTPUT_ON_FAILURE=1&lt;/code&gt; in the environment you can apply this to all &lt;code&gt;ctest&lt;/code&gt; invocations automatically.&lt;/p&gt;&lt;p data-block-key=&quot;65tcn&quot;&gt;There are more environment variables than those even, see &lt;a href=&quot;https://cmake.org/cmake/help/latest/manual/cmake-env-variables.7.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;here&lt;/a&gt; for the full list.&lt;/p&gt;&lt;p data-block-key=&quot;7lc5&quot;&gt;That&apos;s not all, though. Not only can you set environment environment variables understood by CMake, you can also read custom environment variables in your CMake code:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cmake  line-numbers &quot;&gt;if ($ENV{MY_CUSTOM_FLAG})
    do_something_special()
endif()&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;evca8&quot;&gt;This can be useful for customizing the build process when using CMake parameters is not an option.&lt;/p&gt;&lt;p data-block-key=&quot;b27sa&quot;&gt;Environment variables are a small feature that&apos;s easy to overlook, but they can noticeably improve your day-to-day workflow. By moving your personal defaults into your shell configuration, you can spend less time remembering command-line options and more time writing code. And because these settings are applied automatically whenever you create a new build directory, they complement project-specific tools like CMake presets rather than replacing them. Hopefully this tip helps you make your CMake workflow just a little more convenient.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/µ-boosting-your-cmake-productivity-with-environment-variables/&quot;&gt;µ-Boosting Your CMake Productivity with Environment Variables&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>c++</category><category>vscode</category></item><item><title>How long does it take for an Item to become visible?</title><link>https://www.kdab.com/how-long-does-it-take-for-an-item-to-become-visible/</link><guid isPermaLink="true">https://www.kdab.com/how-long-does-it-take-for-an-item-to-become-visible/</guid><description>&lt;p data-block-key=&quot;razlt&quot;&gt;Qt Quick doesn’t drop frames - but it can render them later than expected. This article presents a practical C++ technique to measure when a QQuickItem actually becomes visible, identify late-rendered components, and quantify delays in dropped frames using the Qt scene graph lifecycle.&lt;/p&gt;</description><pubDate>Thu, 04 Jun 2026 14:18:00 GMT</pubDate><content:encoded>&lt;h1&gt;How long does it take for an Item to become visible?&lt;/h1&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Frames skipped counter in application_QML_Blog_Javier&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Frames_skipped_counter_in_application.original.png&quot; class=&quot;Frames skipped counter in application_QML_Blog_Javier&quot; alt=&quot;Frames skipped counter in application_QML_Blog_Javier&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;aw0i2&quot;&gt;Frames skipped counter in application&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;z6f7p&quot;&gt;How long does it take for an &lt;code&gt;Item&lt;/code&gt; that you’ve just loaded to become visible? Answering this question allows for a way to detect what some users would perceive as &quot;frame drops&quot;. I write that in quotes because Qt Quick only draws frames when needed, meaning it doesn&apos;t drop frames; but it can show them later than one would expect. That is what we would like to identify: When components are drawn late, and by how many milliseconds or frames are they late?&lt;/p&gt;&lt;p data-block-key=&quot;akl5c&quot;&gt;I&apos;ve come up with a simple solution - code below the article - on how to measure this. Items being measured must inherit a class based of &lt;code&gt;QQuickItem&lt;/code&gt; that has a connection on &lt;a href=&quot;https://doc.qt.io/qt-6/qquickitem.html#visible-prop&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;QQuickItem::visibleChanged&lt;/code&gt;&lt;/a&gt;. Its visible property should be false by default. When &lt;code&gt;visible&lt;/code&gt; becomes true, a slot will start measuring elapsed time and create a direct connection to &lt;a href=&quot;https://doc.qt.io/qt-6/qquickwindow.html#afterFrameEnd&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;QQuickWindow::afterFrameEnd&lt;/code&gt;&lt;/a&gt;. Once the scene graph has submitted a frame, the slot will take the measurement and disconnect the connection that triggered it to prevent further frames from triggering this event.&lt;/p&gt;&lt;p data-block-key=&quot;29tg9&quot;&gt;That alone isn&apos;t enough, however. If there were other elements on the scene being animated (say from the render thread via an &lt;a href=&quot;https://doc.qt.io/qt-6/qml-qtquick-animator.html&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Animator&lt;/a&gt;), those would trigger a frame swap before our item would have had a chance to be drawn, causing our measurement to be taken prematurely.&lt;br/&gt;We need a way of knowing when the frame that draws our component is the one that got swapped into view. Enter &lt;a href=&quot;https://doc.qt.io/qt-6/qquickitem.html#ensurePolished&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;QQuickItem::ensurePolished&lt;/code&gt;&lt;/a&gt;. Calling this function ensures that &lt;a href=&quot;https://doc.qt.io/qt-6/qquickitem.html#updatePolish&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;QQuickItem::updatePolish&lt;/code&gt;&lt;/a&gt; will be called when the scene graph is ready to render our item. We override &lt;code&gt;QQuickItem::updatePolish&lt;/code&gt; and use it to set a flag that’ll tell us that the next frame to come be displayed will correspond to the component we’re measuring. Lastly, we read this flag during the next call to &lt;i&gt;QQuickWindow::afterFrameEnd&lt;/i&gt;, effectively using it to trigger the elapsed time measurement only when our item is swapped onto the screen.&lt;/p&gt;&lt;p data-block-key=&quot;evsge&quot;&gt;There is a variable amount of time between the last user interaction and the moment a frame can be rendered; because of that, a measurement in milliseconds is only accurate to the average time that it takes for one frame to be rendered immediately after the previous frame. That turns out to be 1 second divided by the display&apos;s refresh rate. We can use &lt;a href=&quot;https://doc.qt.io/qt-6/qscreen.html#refreshRate-prop&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;Qscreen::refreshRate&lt;/code&gt;&lt;/a&gt;, which gives us this value in hertz. For a 60hz display, a frame&apos;s time (T) would be 1000 ms / 60 hz ≃ 16 ms. Any time measured that is between zero and T (16 ms) would mean an instant frame swap. If we divide the measured time by T, and apply a floor function to the result, we get the number of frames dropped while making the component visible, which is a more consistent measurement than the number of milliseconds passed. For a well optimized program the output would be zero, one, or a positive integer very close to that. For more information about the rendering process, you can read &lt;a href=&quot;https://doc.qt.io/qt-6/qtquick-visualcanvas-scenegraph.html#scene-graph-and-rendering&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;scene graph and rendering&lt;/a&gt; from Qt&apos;s documentation.&lt;/p&gt;&lt;p data-block-key=&quot;55thg&quot;&gt;Make C++ items visible during their instantiation, or they won’t show up on screen. This &lt;code&gt;QQuickItem&lt;/code&gt; subclass is different from its parent in that the &lt;code&gt;Item&lt;/code&gt; is not visible by default. We set visible to false from the C++ constructor because the order in which initial properties are evaluated and assigned in QML differs depending on the approach used to instantiate the &lt;code&gt;Item&lt;/code&gt; and assign its initial properties. You may set the initial visible property of an item in QML to false, then make it true during its instantiation as a delegate somewhere, only for the QML Engine to optimally evaluate its initial value solely to true, causing the &lt;code&gt;visibleChanged&lt;/code&gt; signal to never be emitted because there was, effectively, no change to the visible property. Setting the visibility to false from the constructor in C++ is a simple way to guarantee that visibleChanged will be triggered upon any initialization of the visible property in QML.&lt;/p&gt;&lt;p data-block-key=&quot;384fn&quot;&gt;The code for the &lt;code&gt;QQuickItem&lt;/code&gt; subclass described in this article is documented below. Hope you find it useful. Reach out to us if you need help profiling software, would like to receive our training courses, or need help developing tools such as this.&lt;/p&gt;&lt;p data-block-key=&quot;db7k7&quot;&gt;Best regards, Javier&lt;/p&gt;&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;code&quot; data-block-key=&quot;78le0&quot;&gt;Code&lt;/h2&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;#include &amp;lt;QQuickItem&amp;gt;
#include &amp;lt;QQuickWindow&amp;gt;

class TimedItem : public QQuickItem
{
    Q_OBJECT
    QML_ELEMENT

    Q_PROPERTY(qint64 timeToDisplay READ timeToDisplay NOTIFY timeToDisplayChanged FINAL)

public:
    TimedItem(QQuickItem* parent = nullptr) : QQuickItem(parent),
              m_elapsedTimer(new QElapsedTimer())
{
    setVisible(false);
    // When made visible, measure time to display
    QObject::connect(this, &amp;amp;QQuickItem::visibleChanged, this, &amp;amp;TimedItem::startMeasuringTimeToDisplay, Qt::DirectConnection);
};

qint64 timeToDisplay() {
    return m_timeToDisplay;
};

signals:
    void timeToDisplayChanged();

private:
    void startMeasuringTimeToDisplay()
    {
        if (isVisible())
        {
            // Reset
            m_frameReady = false;
            // Attempt to take measurement after frame swaps
            QObject::connect(window(), &amp;amp;QQuickWindow::afterFrameEnd, this, &amp;amp;TimedItem::measure,
            static_cast&amp;lt;Qt::ConnectionType&amp;gt;(Qt::DirectConnection | Qt::UniqueConnection));
            // Force polish, ensuring elapsed measurement is taken on the right frame
            ensurePolished();
            // Take initial measurement
            m_elapsedTimer-&amp;gt;start();
        }
    }

    void updatePolish()
    {
        // The frame for this component will be rendered after this
        m_frameReady = true;
    }

    void measure()
    {
        // This will be called for every frame until the right frame has been rendered
        if (m_frameReady)
        {
            // Measure elapsed time
            m_timeToDisplay = m_elapsedTimer-&amp;gt;elapsed();
            // Prevent measuring further frame
            QObject::disconnect(window(), &amp;amp;QQuickWindow::afterFrameEnd, this, &amp;amp;TimedItem::measure);
            // Propagate measured time
            emit timeToDisplayChanged();
        }
    }

private:
    qint64 m_timeToDisplay = 0;
    QElapsedTimer *m_elapsedTimer;
    bool m_frameReady = false;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;

&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/how-long-does-it-take-for-an-item-to-become-visible/&quot;&gt;How long does it take for an Item to become visible?&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>c++</category><category>performance</category><category>qml</category><category>qt</category><category>ux/ui</category></item><item><title>KDAB at Oxidize 2026, Berlin, September 14–16</title><link>https://www.kdab.com/kdab-at-oxidize-2026-berlin-september-1416/</link><guid isPermaLink="true">https://www.kdab.com/kdab-at-oxidize-2026-berlin-september-1416/</guid><description>&lt;p data-block-key=&quot;btziz&quot;&gt;KDAB is co-hosting Oxidize 2026 in Berlin (September 14–16), the premier conference for engineers using Rust in production. KDAB engineers will lead a hands-on workshop on Rust/C++ interoperability, while CCO Till Adam holds a panel on the evolving Rust job market.&lt;/p&gt;</description><pubDate>Thu, 21 May 2026 07:23:38 GMT</pubDate><content:encoded>&lt;h1&gt;KDAB at Oxidize 2026, Berlin, September 14–16&lt;/h1&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;wxotb&quot;&gt;On September 14-16 2026, the &lt;a href=&quot;https://oxidizeconf.com/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Oxidize Conference&lt;/a&gt; returns to Berlin as the leading event for people using Rust in production. Co-hosted by KDAB and &lt;a href=&quot;https://ferrous-systems.com/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Ferrous Systems&lt;/a&gt;, Oxidize brings together industry leaders, engineers, and open-source contributors to share insights, tools, and success stories.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;why-should-you-attend-oxidize&quot; data-block-key=&quot;6o2ae&quot;&gt;Why should you attend Oxidize?&lt;/h2&gt;&lt;p data-block-key=&quot;fd4b&quot;&gt;Oxidize is the conference for engineers and technical leaders who want to see how Rust is being used in real-world production environments.&lt;/p&gt;&lt;p data-block-key=&quot;34bvp&quot;&gt;From embedded systems and safety-critical software to Rust/C++ interoperability, the event focuses on practical experience, hands-on learning, and honest discussions about adopting Rust at scale. It’s a chance to connect with experienced practitioners, learn from companies already building with Rust, and come away with insights you can apply directly to your own projects.&lt;/p&gt;&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;workshop:-rust-and-c++-can-be-friends&quot; data-block-key=&quot;w6l7q&quot;&gt;Workshop: Rust and C++ Can Be Friends&lt;/h2&gt;&lt;p data-block-key=&quot;95g5i&quot;&gt;At this year’s Oxidize, KDAB engineers Nicolas Fella and Nicolas Qiu Guichard will lead the workshop “&lt;a href=&quot;https://oxidizeconf.com/sessions/rust_and_c_plus_plus_can_be_friends&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Rust and C++ Can Be Friends&lt;/a&gt;”.&lt;/p&gt;&lt;p data-block-key=&quot;977pu&quot;&gt;C++ has been around far longer than Rust, and large codebases exist that are not feasible, economical, or necessary to rewrite in Rust. Therefore, it’s often advisable to integrate Rust into existing C++ codebases. Likewise, many C++ libraries are valuable to integrate into Rust projects.&lt;/p&gt;&lt;p data-block-key=&quot;9r94l&quot;&gt;Attending this workshop gives you a practical view of how mixed Rust/C++ systems are built and maintained in projects. Beyond the technical details, you’ll look at when interoperability makes sense, the trade-offs involved, and common integration issues that tend to appear in larger systems. It’s especially relevant if you’re working with legacy systems or introducing Rust gradually alongside existing C++ codebases.&lt;/p&gt;&lt;p data-block-key=&quot;bcugq&quot;&gt;In the workshop, Nicolas Fella and Nicolas Qiu Guichard will explore:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;baka8&quot;&gt;How to call Rust from C++ and vice versa using CXX&lt;/li&gt;&lt;li data-block-key=&quot;8ra27&quot;&gt;How to integrate a Rust library into a C++ application built with CMake&lt;/li&gt;&lt;li data-block-key=&quot;e1f7m&quot;&gt;How to integrate a C++ library into a Rust application built with Cargo&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;jqk6&quot;&gt;Workshop seats are limited and sell out fast. Get your &lt;a href=&quot;https://ti.to/asquera-event-ug/oxidize-conference-2026&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;tickets&lt;/a&gt; and explore the &lt;a href=&quot;https://oxidizeconf.com/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;full workshop lineup&lt;/a&gt;.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                    &lt;a href=&quot;https://oxidizeconf.com/sessions/rust_and_c_plus_plus_can_be_friends&quot; title=&quot;Oxidize_2026_KDAB_Workshop&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; &gt;
                
                
                &lt;img id=&quot;Oxidize_2026_KDAB_Workshop&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/KDAB_Workshop.original.png&quot; class=&quot;Oxidize_2026_KDAB_Workshop&quot; alt=&quot;Oxidize_2026_KDAB_Workshop&quot;&gt;
                
                
                    &lt;/a&gt;
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;boa1u&quot;&gt;Workshop: Rust and C++ Can Be Friends&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;panel:-navigating-the-rust-job-market:-hiring-and-being-hired&quot; data-block-key=&quot;zlju7&quot;&gt;Panel: Navigating the Rust Job Market: Hiring and Being Hired&lt;/h2&gt;&lt;p data-block-key=&quot;donqg&quot;&gt;As Rust adoption continues to grow, the job market is maturing - and with that comes a new set of challenges for both candidates and employers. &lt;a href=&quot;https://oxidizeconf.com/sessions/panel_navigating_the_rust_job_market&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;This panel&lt;/a&gt; will bridge the gap between expectation and reality in the Rust ecosystem. The panelists will explore the different archetypes of Rust roles - from working on the language itself, to working with it, to joining a company that happens to use it.&lt;/p&gt;&lt;p data-block-key=&quot;e5gfi&quot;&gt;Till Adam (CCO at KDAB) will join industry experts to discuss what companies are really looking for when hiring Rust developers, how candidates can stand out, and how the ecosystem is evolving as Rust adoption increases across industries.&lt;/p&gt;&lt;p data-block-key=&quot;blt8h&quot;&gt;Panelists: Julia Sommer (Ferrous Systems), Julius Gustavsson (Volvo Cars), Tom Fleischmann (Accenture). &lt;i&gt;Additional panelists to be announced.&lt;/i&gt;&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                    &lt;a href=&quot;https://oxidizeconf.com/sessions/panel_navigating_the_rust_job_market&quot; title=&quot;Oxidize_2026_Till_Panel&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot; &gt;
                
                
                &lt;img id=&quot;Oxidize_2026_Till_Panel&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Till_Adam_Panel.original.png&quot; class=&quot;Oxidize_2026_Till_Panel&quot; alt=&quot;Oxidize_2026_Till_Panel&quot;&gt;
                
                
                    &lt;/a&gt;
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;boa1u&quot;&gt;Panel: Navigating the Rust Job Market: Hiring and Being Hired&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;why-kdab-is-investing-in-rust&quot; data-block-key=&quot;44now&quot;&gt;Why KDAB is investing in Rust&lt;/h2&gt;&lt;p data-block-key=&quot;a74dp&quot;&gt;Rust adoption is growing across the industries KDAB works in. It’s especially exciting to hear from practitioners with real-world Rust experience, because those insights help us better guide and support teams as they adopt Rust for their own applications.&lt;/p&gt;&lt;p data-block-key=&quot;50sm&quot;&gt;To support this journey, KDAB has partnered with Ferrous Systems, building on their decade of Rust expertise to strengthen our approach as we continue deepening our involvement in the Rust ecosystem.&lt;/p&gt;&lt;/div&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;meet-the-kdab-team-at-oxidize&quot; data-block-key=&quot;vbsu0&quot;&gt;Meet the KDAB team at Oxidize&lt;/h2&gt;&lt;p data-block-key=&quot;59fj6&quot;&gt;The KDAB team will be available on-site throughout the conference, with live demos and experts on hand to discuss topics including Rust/C++ interoperability, embedded development, modern software architecture, and more.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;wp4yt&quot;&gt;Till Adam (KDAB) and Florian Gilcher (Ferrous Systems) on Oxidize, the applied Rust conference&lt;/p&gt;&lt;/div&gt;







&lt;div class=&quot;cookieconsent-optin-marketing overlay-embed-block&quot;&gt;
    &lt;div style=&quot;padding-bottom: 56.49999999999999%;&quot; class=&quot;responsive-object&quot;&gt;
    &lt;iframe width=&quot;200&quot; height=&quot;113&quot; src=&quot;https://www.youtube.com/embed/pwByWcpe2_k?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen title=&quot;Till and Florian on Oxidize, the applied Rust conference&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;style&gt;
.overlay-embed-block .responsive-object {
    position: relative;
}

.overlay-embed-block .responsive-object iframe,
.overlay-embed-block .responsive-object object,
.overlay-embed-block .responsive-object embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
&lt;/style&gt;




&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;wp4yt&quot;&gt;Oxidize 2024 Recap Video&lt;/p&gt;&lt;/div&gt;







&lt;div class=&quot;cookieconsent-optin-marketing overlay-embed-block&quot;&gt;
    &lt;div style=&quot;padding-bottom: 56.49999999999999%;&quot; class=&quot;responsive-object&quot;&gt;
    &lt;iframe width=&quot;200&quot; height=&quot;113&quot; src=&quot;https://www.youtube.com/embed/0zcYqn3yyn4?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen title=&quot;OxidizeConf 2024 Recap&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;style&gt;
.overlay-embed-block .responsive-object {
    position: relative;
}

.overlay-embed-block .responsive-object iframe,
.overlay-embed-block .responsive-object object,
.overlay-embed-block .responsive-object embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
&lt;/style&gt;


&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/kdab-at-oxidize-2026-berlin-september-1416/&quot;&gt;KDAB at Oxidize 2026, Berlin, September 14–16&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Editor Team</dc:creator><category>rust</category></item><item><title>Singleton Controllers in Times of Declarative QML</title><link>https://www.kdab.com/singleton-controllers-in-times-of-declarative-qml/</link><guid isPermaLink="true">https://www.kdab.com/singleton-controllers-in-times-of-declarative-qml/</guid><description>&lt;p data-block-key=&quot;enlcw&quot;&gt;To make it easier to provide controller objects to QML, André Somers contributed some changes to QQmlEngine and the declarative registration code, so that you can now provide object instances to the QML engine to use as singletons.&lt;/p&gt;</description><pubDate>Thu, 07 May 2026 08:30:44 GMT</pubDate><content:encoded>&lt;h1&gt;Singleton Controllers in Times of Declarative QML&lt;/h1&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;2r6gm&quot;&gt;Controller objects have been the main way to glue your QML UI to your application&apos;s actual implementation of the I/O and business logic. However, over the years, the way to actually expose that controller object has changed. And now, we contributed a change in &lt;code&gt;QQmlEngine&lt;/code&gt; that allows you to change it once again, and we believe: for the better.&lt;/p&gt;&lt;h2 id=&quot;what-are-&amp;quot;controllers&amp;quot;-anyway&quot; data-block-key=&quot;fktq4&quot;&gt;What are &quot;controllers&quot; anyway?&lt;/h2&gt;&lt;p data-block-key=&quot;74pvt&quot;&gt;Conceptually, controllers are a thin glue layer between your business logic and your QML, exposing the data that the GUI needs in a format it can easily use. They are implemented as &lt;code&gt;QObject&lt;/code&gt;-derived instances, usually with properties exposing values that may or may not be writable, as well as potentially some &lt;code&gt;Q_INVOKABLE&lt;/code&gt; methods that can be triggered by the QML and maybe some signals.&lt;/p&gt;&lt;p data-block-key=&quot;3spbd&quot;&gt;Usually, these controllers are specific to a single logical group of values and functions within the wider application. An application may have a hand-full to dozens of them for a big system. Models exposing collections of data are usually made available as read-only properties returning a &lt;code&gt;QAbstractItemModel&lt;/code&gt;-derived data model on these controllers.&lt;/p&gt;&lt;p data-block-key=&quot;3o2sq&quot;&gt;Often, these controllers need to be instantiated with some initialization, as they need references to the business-logic objects they expose to the GUI, listen for signals to get notifications of changes, etc. And that&apos;s where the trouble starts...&lt;/p&gt;&lt;h2 id=&quot;pre-qt-6&quot; data-block-key=&quot;zxg61&quot;&gt;Pre-Qt 6&lt;/h2&gt;&lt;h3 id=&quot;context-properties&quot; data-block-key=&quot;eavwy&quot;&gt;Context properties&lt;/h3&gt;&lt;p data-block-key=&quot;15l2r&quot;&gt;In the early days of QML, one would often use controller instances exposed to QML as context properties. Doing that allowed one to instantiate the controllers under control of C++, giving it all the references the objects needed at that time. We would often expose them to QML using a naming pattern like starting the name with a double underscore &lt;code&gt;__someController&lt;/code&gt; so that it was easy to recognize in the QML code. Using context properties however is no longer recommended. Their lookup is slow, and the QML compilers cannot reason about them, so code using them cannot be optimized. Nor is tooling available to help the QML programmer, as code completion and the likes are not possible.&lt;/p&gt;&lt;h3 id=&quot;singleton-instances&quot; data-block-key=&quot;70tnc&quot;&gt;Singleton Instances&lt;/h3&gt;&lt;p data-block-key=&quot;6jkss&quot;&gt;Then came the &lt;code&gt;qmlRegisterSingletonInstance&lt;/code&gt; method. This method allowed one to register a QML singleton, but it would return the instance that you passed it as an argument and that you could instantiate however you needed. That was a good solution, but it didn&apos;t have a long useful life as it didn&apos;t mesh well with the declarative registration and it had issues with the one instance being the instance for &lt;i&gt;every&lt;/i&gt; QML engine in your application (if you had more than one).&lt;/p&gt;&lt;h2 id=&quot;post-qt-6&quot; data-block-key=&quot;s4n26&quot;&gt;Post-Qt 6&lt;/h2&gt;&lt;p data-block-key=&quot;3dcoj&quot;&gt;Since Qt 6, the recommended way to write QML is to create QML modules using declarative registration for C++-based objects. That has many benefits in terms of tooling and optimization, so it&apos;s good practice to do this. But it also meant that since Qt 6, one could no longer mix-and match imperative registration with declarative: you either used the one, or the other; which rendered the &lt;code&gt;qmlRegisterSingletonInstance&lt;/code&gt; method above useless.&lt;/p&gt;&lt;p data-block-key=&quot;btlk2&quot;&gt;There are many possible approaches that I have seen being applied to still control the creation of controller objects, usually by registering a singleton that has a static &lt;code&gt;create&lt;/code&gt; factory function and returning some C++ singletons there or something along those lines. That works, but isn&apos;t very elegant. An alternative approach is using initial properties on the root object, but that either requires accessessing the root id from other QML files or propagating the controllers all the way down the stack of items. Neither is a great solution for different reasons. My colleague Javier Cordero Pérez is making a couple of videos about ways to do this, so I won&apos;t go into detail here. These videos will be added here once they have been released.&lt;/p&gt;&lt;h2 id=&quot;new-approach&quot; data-block-key=&quot;yslss&quot;&gt;New approach&lt;/h2&gt;&lt;p data-block-key=&quot;dof5b&quot;&gt;That building this connection between C++ and QML was so inelegant - despite being so important - inspired me to finally take matters into my own hands and write a patch.&lt;/p&gt;&lt;p data-block-key=&quot;vg8t&quot;&gt;The result is available starting with Qt 6.12 onward and it combines the good things of &lt;code&gt;qmlRegisterSingletonInstance&lt;/code&gt; and the declarative registration: you still register your controller type as a QML singleton so that the type is fully known by the tooling and access to it can be optimized. But we gain back the ability to provide a ready-made instance to the QML engine.&lt;/p&gt;&lt;h3 id=&quot;setexternalsingletoninstance&quot; data-block-key=&quot;5qpzh&quot;&gt;setExternalSingletonInstance&lt;/h3&gt;&lt;p data-block-key=&quot;5ljrl&quot;&gt;The API on &lt;code&gt;QQmlEngine&lt;/code&gt; gained a single new method: &lt;a href=&quot;https://doc-snapshots.qt.io/qt6-dev/qqmlengine.html#setExternalSingletonInstance&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;QQmlEngine::setExternalSingletonInstance&lt;/code&gt;&lt;/a&gt;. It allows you to provide an instance of a type declared as a singleton as the instance to use in any QML running in that engine, just like you could with &lt;code&gt;qmlRegisterSingletonInstance&lt;/code&gt;. In contrast to that old registration function, however, you call this method on your specific QQmlEngine instance. Note that the type has to be (declaratively) registered as a singleton type for this call to work. If you are using more than one engine, it is up to you to decide if you want to provide the same instance to these different engines, or have separate instances.&lt;/p&gt;&lt;p data-block-key=&quot;5ognp&quot;&gt;This simple method gives you back an elegant, supported way to fully control the instantiation of the QML singleton, and thus easily connect it to your business logic or whatever else you need to with it. However, it is up to you make sure that you do this call before any QML code actually tries to access the singleton. Otherwise, the engine will (try to) create it&apos;s own instance as it used to. You cannot replace an already existing singleton instance, so once there is one, it is &lt;i&gt;the&lt;/i&gt; one.&lt;/p&gt;&lt;p data-block-key=&quot;cslhj&quot;&gt;It’s up to you to make sure that the provided singleton instance outlives the QML that depends on it. You can do that in any way that works in your context, but you could consider parenting the instance to the &lt;code&gt;QQmlEngine&lt;/code&gt; instance, ordering the variables containing them on the stack correctly, or using &lt;code&gt;QQmlEngine::setObjectOwnership&lt;/code&gt; to hand ownership of the singleton to the QML engine.&lt;/p&gt;&lt;h3 id=&quot;qml_uncreatable-for-singletons&quot; data-block-key=&quot;49k53&quot;&gt;QML_UNCREATABLE for singletons&lt;/h3&gt;&lt;p data-block-key=&quot;75lid&quot;&gt;If you are providing your QML singleton instance yourself anyway, you logically also don&apos;t &lt;i&gt;need&lt;/i&gt; it to be creatable by the engine either - although, it still can be, of course. If your controller type has a non-default constructor - perhaps to take in some references to your business logic instances - you can now mark your singleton with &lt;a href=&quot;https://doc-snapshots.qt.io/qt6-dev/qqmlintegration-h.html#QML_UNCREATABLE&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;&lt;code&gt;QML_UNCREATABLE&lt;/code&gt;&lt;/a&gt;, just like you can with other QML types. If you do that, you no longer need to supply a factory function (and even if you do, it won&apos;t be used).&lt;/p&gt;&lt;p data-block-key=&quot;7pv39&quot;&gt;Of course, if you mark a singleton as uncreatable, it is up to you to make sure you actually supply an instance via &lt;code&gt;QQmlEngine::setExternalSingletonInstance&lt;/code&gt; before the singleton is needed from QML.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/singleton-controllers-in-times-of-declarative-qml/&quot;&gt;Singleton Controllers in Times of Declarative QML&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>qml</category><category>qt</category></item><item><title>Building Reusable Slint UI Libraries with Rust Crates</title><link>https://www.kdab.com/building-reusable-slint-ui-libraries-with-rust-crates/</link><guid isPermaLink="true">https://www.kdab.com/building-reusable-slint-ui-libraries-with-rust-crates/</guid><description>&lt;p data-block-key=&quot;7fc0t&quot;&gt;Slint now supports exporting components and types from Rust crates, enabling developers to build reusable UI libraries and extensions. This post explains how the new experimental module build feature works, what changed in the slint-compiler, and how to create and consume a custom Slint library in your application.&lt;/p&gt;</description><pubDate>Wed, 22 Apr 2026 09:01:00 GMT</pubDate><content:encoded>&lt;h1&gt;Building Reusable Slint UI Libraries with Rust Crates&lt;/h1&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8g30m&quot;&gt;&lt;a href=&quot;https://slint.dev/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;Slint&lt;/a&gt; is a UI framework, implemented in Rust, with support for C++, WASM, and Python. Find out more at &lt;a href=&quot;https://slint.dev/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://slint.dev&lt;/a&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;441lr&quot;&gt;One thing that is needed for any UI framework is the possibility to extend the framework with your own custom UI components. That may not sound particularly special - Qt and QML already offer plenty of ways to do this, which is the reason why I won’t get into that here. As a modern programming language, Rust offers additional functionality through “crates,” which are published and distributed on &lt;a href=&quot;http://crates.io/&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;crates.io&lt;/a&gt;, the central package registry for Rust. To extend the Slint UI framework, the natural way to accomplish this is to implement a Rust crate. In my case, this was done to provide a custom chart library. Slint is a declarative UI, meaning the UI components are described in the Slint language. For the chart library, the UI and data types are implemented in the Slint language (.slint files). The rendering of the chart is implemented in native Rust code.&lt;/p&gt;&lt;p data-block-key=&quot;6fqjc&quot;&gt;So, what&apos;s the deal here, you may ask? Let&apos;s get into how Slint works under the hood. The .slint files are compiled by the slint-compiler into Rust-code (or into C++ code in the case of a C++ application). The output from the slint-compiler is included into the Rust application via the &lt;code&gt;slint::include_modules!()&lt;/code&gt; macro. So far, so good. The Slint application (which makes use of the chart library) needs to use UI components and types that are provided by the chart library / crate. Until now, it has not been possible to export components and types provided by previously slint-compiled Rust code.&lt;br/&gt;&lt;/p&gt;&lt;p data-block-key=&quot;12uom&quot;&gt;This is what I have been working on, implemented in the following PR: &lt;a href=&quot;https://github.com/slint-ui/slint/pull/9329&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/slint-ui/slint/pull/9329&lt;/a&gt;. The changes were merged upstream and became part of the 1.14 release. They were introduced as an experimental feature that needs to be explicitly enabled (&lt;code&gt;slint-build/experimental-module-builds&lt;/code&gt;). Since then, Slint has moved on to the &lt;a href=&quot;https://github.com/slint-ui/slint/releases/tag/v1.16.0&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;1.16 release&lt;/a&gt; since April 2026. Implementing this required substantial changes to the slint-compiler.&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;c0rn6&quot;&gt;When building the Rust crate, the slint-compiler needs to know that it is building a library / extension. A Slint library does not have a MainWindow, and some types in the generated Rust code needs to be declared &lt;code&gt;pub&lt;/code&gt; so the application can access them. To configure the slint-compiler, the &lt;code&gt;build.rs&lt;/code&gt; looks like this&lt;b&gt;:&lt;/b&gt;&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-rust  line-numbers &quot;&gt;fn main() {
    let config = slint_build::CompilerConfiguration::new().as_library(&amp;quot;MyLibrary&amp;quot;).rust_module(&amp;quot;mymodule&amp;quot;);
    slint_build::compile_with_config(&amp;quot;mylib.slint&amp;quot;, config);
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;ul&gt;&lt;li data-block-key=&quot;8g30m&quot;&gt;For the application we just need to add the dependency in &lt;code&gt;Cargo.toml&lt;/code&gt;:&lt;/li&gt;&lt;/ul&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-toml  line-numbers &quot;&gt;[dependencies]
my-library = { ... }&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8g30m&quot;&gt;In the .slint file, using a type or component exported by &lt;code&gt;my-library&lt;/code&gt; is done as follows&lt;b&gt;:&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-slint  line-numbers &quot;&gt;import { Foo } from &amp;quot;@MyLibrary&amp;quot;&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8g30m&quot;&gt;The slint-compiler will automatically determine that the import &lt;code&gt;@MyLibrary&lt;/code&gt; comes from the Rust &lt;code&gt;my-library&lt;/code&gt; crate. This “magic” is actually done by the Cargo build system. When building &lt;code&gt;my-library&lt;/code&gt;, the following environment variables are set:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-plain  line-numbers &quot;&gt;DEP_MYLIBRARY_SLINT_LIBRARY_MODULE=mymodule
DEP_MYLIBRARY_SLINT_LIBRARY_NAME=MyLibrary
DEP_MYLIBRARY_SLINT_LIBRARY_PACKAGE=my-library
DEP_MYLIBRARY_SLINT_LIBRARY_SOURCE=/absolute/path/to/mylibrary.slint&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8g30m&quot;&gt;These are picked up by the slint-compiler, which then knows that imports from &lt;code&gt;@MyLibrary&lt;/code&gt; come from the &lt;code&gt;my-library&lt;/code&gt; crate. The slint-compiler still needs to parse &lt;code&gt;mylibrary.slint&lt;/code&gt; and add the used types into the slint-compiler type loader. In the final step, when Rust code is generated, imports from &lt;code&gt;@MyLibrary&lt;/code&gt; result in Rust &lt;code&gt;use ...&lt;/code&gt; statements instead of generated code.&lt;/p&gt;&lt;p data-block-key=&quot;98r3k&quot;&gt;Now let’s look at Slint globals, using a simple example&lt;b&gt;:&lt;/b&gt;&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-slint  line-numbers &quot;&gt;export global MyAppData {
    in property &amp;lt;int&amp;gt; some-value: 42;
    callback update-data();
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;8g30m&quot;&gt;Conceptually, Slint global components are just singleton instances. Technically, all Slint global components are created when the application MainWindow is created and are associated with it. When the MainWindow is destroyed, all global components are destroyed as well. The slint-compiler now needs to know which external libraries are imported, so it can also create and initialize the global components defined in those libraries. A crate can also depend on another crate. For example, let&apos;s say we have crate A, B , and C, both crate A and B have a dependency to crate C. The Slint globals for crate C will be instantiated twice. Due to that the current implementation does not yet cover all edge cases related to creating and initializing Slint global components. This is something that needs to be improved, but apart from that, it works fine.&lt;/p&gt;&lt;p data-block-key=&quot;5p77n&quot;&gt;There is still quite some work to do before this feature can move out of experimental status, but so far it is promising and already good enough to start building libraries and extensions for the Slint UI framework.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/building-reusable-slint-ui-libraries-with-rust-crates/&quot;&gt;Building Reusable Slint UI Libraries with Rust Crates&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>rust</category><category>slint</category></item><item><title>Bind QML Values across an Arbitrary Number of Elements</title><link>https://www.kdab.com/bind-qml-values-across-an-arbitrary-number-of-elements/</link><guid isPermaLink="true">https://www.kdab.com/bind-qml-values-across-an-arbitrary-number-of-elements/</guid><description>&lt;p data-block-key=&quot;rlqes&quot;&gt;Synchronize properties across dynamically instantiated QML elements, using a C++ singleton that acts as a message broker and recursive signal-slot connections. This design enables flexible and scalable value synchronization across components, with minimal coupling between UI and logic.&lt;/p&gt;</description><pubDate>Thu, 19 Mar 2026 08:26:39 GMT</pubDate><content:encoded>&lt;h1&gt;Bind QML Values across an Arbitrary Number of Elements&lt;/h1&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gg3t3&quot;&gt;A while back I was writing a program that could instantiate an arbitrary number of windows containing controls synchronized across them. How a control would be synchronized would depend on a condition that determined which window instances would be linked to other instances. There are a few ways this could be implemented. In this entry I&apos;ll share my approach, in which I used a singleton C++ class serving as a message broker to bind properties across window instances.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Properties bound across multiple instances of a window_1_QML_Blog_Javier&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Properties_bound_across_multiple_instances_of_a.original.jpg&quot; class=&quot;Properties bound across multiple instances of a window_1_QML_Blog_Javier&quot; alt=&quot;Properties bound across multiple instances of a window_1_QML_Blog_Javier&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gjgxd&quot;&gt;Properties bound across multiple instances of a window&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;the-app:-display-to-light-panels&quot; data-block-key=&quot;4wd6v&quot;&gt;The App: Display to Light Panels&lt;/h2&gt;&lt;p data-block-key=&quot;770kf&quot;&gt;The software I wrote this for is a small app that allows you to have many windows open, all synchronized to display a single color on a per-monitor basis. The idea behind this is for users to adjust the light that comes off their monitors and use it to illuminate their faces when recording a video or taking pictures. By having individual windows be synchronized, users can continue to interact with the computer through their displays (rather inconveniently), while simultaneously using them to illuminate themselves.&lt;/p&gt;&lt;p data-block-key=&quot;clvra&quot;&gt;This is, by no means, a replacement for a proper recording setup. Some of you will know that a light source placed in front of the subject can serve as either a nice &quot;fill light&quot; or a &quot;scary spotlight&quot;, depending on the height and size of the source. Therefore, this should be complemented with other sources of light; ideally ambient light and a top light, to achieve a nice look. If the app seems useful to you, there&apos;s a link to it at the end of this article. That&apos;s enough gaffer speak for today. Let&apos;s talk about the code.&lt;/p&gt;&lt;h2 id=&quot;the-code&quot; data-block-key=&quot;4ug9g&quot;&gt;The Code&lt;/h2&gt;&lt;p data-block-key=&quot;6u7bp&quot;&gt;When writing code, one of my main concerns is always long-term maintainability. For that reason, I prefer to connect different parts of code in explicit, and easy to follow ways. One of such ways is passing values through a hierarchy of components; that is generally easy to track and produces well performing code. However, that approach can become unviable when connecting dynamically instantiated items to other dynamically instantiated items. A better solution in this instance is to use signals and slots to interconnect the items via a message broker class, done in C++. Each item would have a model or backend class in C++ and those classes would have the message broker in common. Lastly, the properties would be exposed to QML through &lt;code&gt;Q_PROPERTY&lt;/code&gt; and updated via your control&apos;s signal handlers.&lt;/p&gt;&lt;h2 id=&quot;message-broker-singleton&quot; data-block-key=&quot;01urs&quot;&gt;Message Broker Singleton&lt;/h2&gt;&lt;p data-block-key=&quot;fa272&quot;&gt;The message broker needs to be a singleton. That way there&apos;s only one instance of the broker in memory and all instantiated objects interface with the same broker. Our message broker only needs to provide the signals that will be used for routing properties. The actual connections that the routing involves are to be done from the outside. As such, a broker class would look like this:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// internalmessagebroker.hpp
// Singleton broker class contains signals that serve as pipes
// for different parts of a program to communicate with each other.

#pragma once

#include &amp;lt;QObject&amp;gt;

class InternalMessageBroker : public QObject
{
    Q_OBJECT

// Hide regular constructor
private:
    InternalMessageBroker() = default;

public:
    // Disable copy constructor
    InternalMessageBroker(const InternalMessageBroker&amp;amp; obj) = delete;
    InternalMessageBroker&amp;amp; operator=(InternalMessageBroker const&amp;amp;) = delete;
    static std::shared_ptr&amp;lt;InternalMessageBroker&amp;gt; instance()
    {
        static std::shared_ptr&amp;lt;InternalMessageBroker&amp;gt; sharedPtr{new InternalMessageBroker};
        return sharedPtr;
    }

// This is where all the signals would go
signals:
    void broadcastAPropertyChange(int value, bool broadcast);
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;



&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;connecting-properties&quot; data-block-key=&quot;qnyhk&quot;&gt;Connecting Properties&lt;/h2&gt;&lt;p data-block-key=&quot;57h0&quot;&gt;Then we have the class or classes that would connect the properties together. On my Display Panels app, all controls and visual features come from QML, meaning I only have to concern myself with interconnecting the properties. To that end, I&apos;ve created a class based on &lt;code&gt;QObject&lt;/code&gt; and instantiated it within the delegate of a QML Instantiator. This class is only for managing property data, so I refer to it as a model class, called &lt;code&gt;PropertiesModel&lt;/code&gt;:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-qml  line-numbers &quot;&gt;// Main.qml
// Here are 3 windows, each with a PropertiesModel,
// that allows them all to share a binding to aProperty
// across all window instantiatons.

import QtQuick
import QtQuick.Window
import QtQuick.Layouts
import QtQuick.Controls

import com.kdab.example

Item {
    Instantiator {
        model: 3
        delegate: Window {
            PropertiesModel {
                id: propertiesModel
                aProperty: 1
            }
            ColumnLayout {
                anchors.fill: parent
                Text {
                    text: propertiesModel.aProperty
                }
                Slider {
                    id: hueSlider
                    value: propertiesModel.aProperty
                    Layout.fillWidth: true
                    onMoved: {
                        propertiesModel.aProperty = value;
                    }
                }
            }
        }
    }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;669ti&quot;&gt;Each property of our class is to be declared using a &lt;code&gt;Q_PROPERTY&lt;/code&gt; macro with a getter, a setter, and a notifier. The getter and the notifier have nothing out of the ordinary...&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// An ordinary getter
int PropertiesModel::aProperty()
{
    return m_aProperty;
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;




&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// An ordinary notifier, form propertiesmodel.h
signals:
    void aPropertyChanged();&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;669ti&quot;&gt;However, the setter must be able to distinguish between when its call is the product of a user interaction and when it comes from the message broker. We accomplish this by having the setter accept a boolean argument (broadcast) that will be used to determine whether the value being set should be sent through the message broker or notified back to QML for the UI to be updated. When a setter is first called, the value being set should be broadcasted and only on its way back should the UI be updated.&lt;/p&gt;&lt;p data-block-key=&quot;7krnb&quot;&gt;There are a few ways we could make sure that the value is always broadcasted first. We could set broadcast to true by default, or have two setter functions: a private one taking in both the value and broadcast arguments and a public one that only takes-in the value and calls the private function with broadcast set to true. Another option is to have broadcast be an enum with only two possible values. That would produce more readable code, however, I didn&apos;t worry about that in my code because broadcast is only to be used on calls to the private setter.&lt;/p&gt;&lt;p data-block-key=&quot;2ij84&quot;&gt;Upon the private setter being called by the public setter, it will emit the broker&apos;s broadcast signal and that signal will in turn call the calling private setter for a second time (as well as the private setters of all other instances of &lt;code&gt;PropertiesModel&lt;/code&gt;; those being called for the first time). When the private setter calls the broker that calls back to the private setter, it also passes broadcast set to false. All other instances will then evaluate the value of broadcast to be false determining that the UIs should be updated and preventing an infinite loop.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// Private setter implementation for a property that&amp;#x27;s being broadcasted
void PropertiesModel::setAProperty(const int value, const bool broadcast)
{
    if (broadcast)
        emit m_broker.get()-&amp;gt;broadcastAPropertyChange(value, false);
    else if (m_currentScreen == screenName) {
        m_aProperty = value;
        emit screenSaturationChanged();
    }
}

// Publicly exposed setter prevents the broadcast argument from being
// specified by other callers
void PropertiesModel::setAProperty(const int value)
{
    // Always broadcast properties not being set by the message broker
    setAProperty(value, true);
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;


&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;669ti&quot;&gt;To complete this loop as described, we need to connect the signal from the broker to the private setter of the &lt;code&gt;PropertiesModel&lt;/code&gt; class whenever a new copy is instantiated. The best place to accomplish that is from the class&apos; constructor, like so:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// The constructor is used to connect signals from the broker to setter properties
PropertiesModel::PropertiesModel(QObject* parent)
    : QObject { parent }
{
    m_mb = InternalMessageBroker::instance();
    // Connections take place after the class has been instantiated
    // and its QML properties parsed.
    QTimer::singleShot(0, this, [this] () {
        connect(m_mb.get(), &amp;amp;InternalMessageBroker::aPropertyChange,
        this, &amp;amp;ScreenModel::setAProperty);
    });
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;



&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;qtimer-to-delay-initialization&quot; data-block-key=&quot;f44wk&quot;&gt;QTimer to Delay Initialization&lt;/h2&gt;&lt;p data-block-key=&quot;7r9tu&quot;&gt;If you&apos;ve been reading the blocks of code that accompany this article, you may be wondering &quot;Why is there a &lt;code&gt;singleShot&lt;/code&gt; &lt;code&gt;QTimer&lt;/code&gt; there?&quot; When the &lt;code&gt;PropertiesModel&lt;/code&gt; class is instantiated via QML, any connected properties that have values assigned from QML code will trigger the &lt;code&gt;Q_PROPERTY&lt;/code&gt;&apos;s setter function. This will happen once per instantiation. If the broker were connected, it would broadcast the value set to all currently instantiated instances every single time that a new instance is added. To prevent that, we must not make the connection to the broker until after a class has been fully initialized. A single shot timer can be used to accomplish that; setting its delay to 0 will ensure that it is run immediately after all properties have been evaluated.&lt;/p&gt;&lt;p data-block-key=&quot;dlka2&quot;&gt;In the end, this is what the &lt;code&gt;PropertyMode&lt;/code&gt;l header would look like:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// propertiesmodel.h
#pragma once

#include &amp;quot;internalmessagebroker.hpp&amp;quot;

#include &amp;lt;QQmlEngine&amp;gt;

class PropertiesModel : public QObject {
    Q_OBJECT
    QML_ELEMENT

    Q_PROPERTY(int aProperty READ aProperty WRITE setAProperty NOTIFY aPropertyChanged FINAL)
public:
    explicit PropertiesModel(QObject* parent = nullptr);

    int aProperty();
    void setAProperty(const int value);

signals:
    void aPropertyChanged();

private: 
    void setAProperty(const int value, const bool broadcast);

    std::shared_ptr&amp;lt;InternalMessageBroker&amp;gt; m_mb;
    int m_aProperty;
};&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;



&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;conditional-propagation&quot; data-block-key=&quot;0xkmg&quot;&gt;Conditional propagation&lt;/h2&gt;&lt;p data-block-key=&quot;aoaf7&quot;&gt;If a condition must be met for the propagated value to result in an update, then, in addition to &lt;i&gt;value&lt;/i&gt; and &lt;i&gt;broadcast&lt;/i&gt;, you should also broadcast any other values that are required to for such condition to be met. Pass those as arguments to the setter&apos;s and the broker&apos;s signal. Condition validation would then take place within the base case of the private setter. Here&apos;s a commented snippet of what that looks like on the Display to Light Panels app that this article was based on:&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;formatted-code&quot;&gt;
    &lt;pre&gt;&lt;code class=&quot;language-cpp  line-numbers &quot;&gt;// In the original code what here I named broadcast used to be named spread.
void ScreenModel::setScreenHue(const int hue, const bool spread=true, const QString &amp;amp;screenName=&amp;quot;s&amp;quot;)
{
    if (spread)
        emit m_mb.get()-&amp;gt;spreadHueChange(hue, false, m_currentScreen);
    // The incoming value is only accepted if screenName matches the screen
    // that the window is at and discarded otherwise.
    else if (m_currentScreen == screenName) {
        m_screens[m_currentScreen].hue = hue;
        emit screenHueChanged();
    }
}&lt;/code&gt;&lt;/pre&gt;
 &lt;/div&gt;




&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-50 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Properties bound across instances of a window_2_QML_Blog_Javier&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Properties_bound_across_instances_of_a_window.original.jpg&quot; class=&quot;Properties bound across instances of a window_2_QML_Blog_Javier&quot; alt=&quot;Properties bound across instances of a window_2_QML_Blog_Javier&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gjgxd&quot;&gt;Properties bound across instances of a window&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;real-world-example&quot; data-block-key=&quot;fgh11&quot;&gt;Real World Example&lt;/h2&gt;&lt;p data-block-key=&quot;cbkgp&quot;&gt;For a real world application, you can read the code for Display to Light Panels, the app that inspired this article, at: &lt;a href=&quot;https://github.com/Cuperino/Display-to-Light-Panels&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;https://github.com/Cuperino/Display-to-Light-Panels&lt;/a&gt;&lt;/p&gt;&lt;p data-block-key=&quot;82jef&quot;&gt;If you need help solving architecture problems, such as this one, reach out to us and we will gladly find ways in which we can help.&lt;/p&gt;&lt;/div&gt;&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/bind-qml-values-across-an-arbitrary-number-of-elements/&quot;&gt;Bind QML Values across an Arbitrary Number of Elements&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><category>c++</category><category>desktop</category><category>qml</category><category>qt</category><category>ux/ui</category></item><item><title>Hotspot v1.6.0 released</title><link>https://www.kdab.com/hotspot-v1-6-0-released/</link><guid isPermaLink="true">https://www.kdab.com/hotspot-v1-6-0-released/</guid><description>&lt;p data-block-key=&quot;1sw01&quot;&gt;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. Our goal with Hotspot is to offer a modern alternative to perf report, making performance analysis on Linux […]&lt;/p&gt;</description><pubDate>Fri, 13 Mar 2026 07:19:00 GMT</pubDate><content:encoded>&lt;h1&gt;Hotspot v1.6.0 released&lt;/h1&gt;
&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;3x6h0&quot;&gt;&lt;a href=&quot;https://www.kdab.com/hotspot-a-gui-for-perf-report-video/&quot;&gt;Hotspot&lt;/a&gt; is a standalone GUI designed to provide a user-friendly interface for analyzing performance data. It takes a &lt;code&gt;perf.data&lt;/code&gt; 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 &lt;code&gt;perf report&lt;/code&gt;, making performance analysis on Linux systems more intuitive and efficient.&lt;/p&gt;&lt;p data-block-key=&quot;c275l&quot;&gt;Version 1.6.0 introduces powerful new features, improved workflows, and numerous bug fixes to make profiling even smoother.&lt;/p&gt;&lt;h2 id=&quot;changelog-for-hotspot-v1.6.0&quot; data-block-key=&quot;fotow&quot;&gt;ChangeLog for Hotspot v1.6.0&lt;/h2&gt;&lt;p data-block-key=&quot;duuev&quot;&gt;&lt;a href=&quot;https://github.com/KDAB/hotspot/releases/tag/v1.6.0&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;This release&lt;/a&gt; focuses on usability improvements and extended analysis capabilities. The most notable additions are:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;8eokv&quot;&gt;Support for archived perf files (e.g. &lt;code&gt;perf.data.zip&lt;/code&gt;)&lt;/li&gt;&lt;li data-block-key=&quot;6s6o4&quot;&gt;Regex filtering in the flamegraph&lt;/li&gt;&lt;li data-block-key=&quot;6dmsd&quot;&gt;Tracepoint support&lt;/li&gt;&lt;li data-block-key=&quot;fkvnu&quot;&gt;Various bug fixes and stability improvements&lt;/li&gt;&lt;/ul&gt;&lt;h2 id=&quot;open-archived-perf-files-directly&quot; data-block-key=&quot;ac2un&quot;&gt;Open Archived perf Files Directly&lt;/h2&gt;&lt;p data-block-key=&quot;7v5j4&quot;&gt;Hotspot can now open perf recordings stored inside archives such as &lt;code&gt;perf.data.zip&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;23rsk&quot;&gt;This simplifies sharing profiling results, storing CI artifacts, and working with compressed recordings - no manual extraction required.&lt;/p&gt;&lt;h2 id=&quot;regex-in-the-flamegraph&quot; data-block-key=&quot;92ca2&quot;&gt;Regex in the Flamegraph&lt;/h2&gt;&lt;p data-block-key=&quot;81umj&quot;&gt;The Flamegraph view now supports &lt;b&gt;regular expression filtering&lt;/b&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;2el70&quot;&gt;This makes it much easier to:&lt;/p&gt;&lt;ul&gt;&lt;li data-block-key=&quot;gn9m&quot;&gt;Match complex symbol patterns&lt;/li&gt;&lt;li data-block-key=&quot;5m0p9&quot;&gt;Focus on specific subsystems or namespaces&lt;/li&gt;&lt;li data-block-key=&quot;1qokc&quot;&gt;Quickly narrow down large profiling datasets&lt;/li&gt;&lt;/ul&gt;&lt;p data-block-key=&quot;6s8g3&quot;&gt;Especially for large C++ codebases, regex search significantly speeds up navigation.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Hotspot_V1.6.0_Regex_Example&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Clipboard_-_March_3_2026_1_17_PM.original.png&quot; class=&quot;Hotspot_V1.6.0_Regex_Example&quot; alt=&quot;Hotspot_V1.6.0_Regex_Example&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gcck3&quot;&gt;Regex in the flamegraph&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;tracepoint-support&quot; data-block-key=&quot;rwr7g&quot;&gt;Tracepoint Support&lt;/h2&gt;&lt;p data-block-key=&quot;243vm&quot;&gt;Hotspot 1.6.0 introduces support for &lt;b&gt;tracepoints&lt;/b&gt; captured via &lt;code&gt;perf&lt;/code&gt;.&lt;/p&gt;&lt;p data-block-key=&quot;6ps2v&quot;&gt;This enables analysis of event-based data in addition to traditional sampling, giving deeper insight into system and runtime behavior.&lt;/p&gt;&lt;/div&gt;


&lt;div class=&quot;image-variable-size-block&quot;&gt;
    &lt;div class=&quot;image-variable-positioning-block right-margin-auto left-margin-auto width-100 &quot; &gt;
            &lt;div class=&quot;image-variable-size-image&quot;&gt;
                
                
                
                &lt;img id=&quot;Hotspot_V1.6.0_Tracepoints_Example&quot; src=&quot;https://eu-central-1.linodeobjects.com/wagtail-production/images/Clipboard_-_March_3_2026_1_23_PM.original.png&quot; class=&quot;Hotspot_V1.6.0_Tracepoints_Example&quot; alt=&quot;Hotspot_V1.6.0_Tracepoints_Example&quot;&gt;
                
                
        &lt;/div&gt;
        &lt;div class=&quot;image-variable-size-caption text-center&quot;&gt;&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;gcck3&quot;&gt;Tracepoints&lt;/p&gt;&lt;/div&gt;&lt;/div&gt;
    &lt;/div&gt;
&lt;/div&gt;

&lt;div class=&quot;rich-text&quot;&gt;&lt;h2 id=&quot;bug-fixes-and-improvements&quot; data-block-key=&quot;mapco&quot;&gt;Bug Fixes and Improvements&lt;/h2&gt;&lt;p data-block-key=&quot;eh5nf&quot;&gt;As usual, this release also includes numerous smaller fixes, UI refinements, and internal cleanups to improve overall stability and user experience.&lt;/p&gt;&lt;p data-block-key=&quot;a8i75&quot;&gt;For a complete overview of all changes, see the &lt;a href=&quot;https://github.com/KDAB/hotspot/releases/tag/v1.6.0&quot; rel=&quot;noopener noreferrer&quot; target=&quot;_blank&quot;&gt;full changelog&lt;/a&gt; on GitHub.&lt;/p&gt;&lt;p data-block-key=&quot;csck&quot;&gt;Happy profiling!&lt;/p&gt;&lt;h2 id=&quot;videos&quot; data-block-key=&quot;pn4uq&quot;&gt;Videos&lt;/h2&gt;&lt;p data-block-key=&quot;dmlud&quot;&gt;Hotspot - A GUI for perf report:&lt;/p&gt;&lt;/div&gt;







&lt;div class=&quot;cookieconsent-optin-marketing overlay-embed-block&quot;&gt;
    &lt;div style=&quot;padding-bottom: 56.49999999999999%;&quot; class=&quot;responsive-object&quot;&gt;
    &lt;iframe width=&quot;200&quot; height=&quot;113&quot; src=&quot;https://www.youtube.com/embed/6ogEkQ-vKt4?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen title=&quot;Hotspot – A GUI for perf report&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;style&gt;
.overlay-embed-block .responsive-object {
    position: relative;
}

.overlay-embed-block .responsive-object iframe,
.overlay-embed-block .responsive-object object,
.overlay-embed-block .responsive-object embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
&lt;/style&gt;



&lt;div class=&quot;rich-text&quot;&gt;&lt;p data-block-key=&quot;3x6h0&quot;&gt;Hotspot Demo from Embedded World 2022:&lt;/p&gt;&lt;/div&gt;







&lt;div class=&quot;cookieconsent-optin-marketing overlay-embed-block&quot;&gt;
    &lt;div style=&quot;padding-bottom: 56.49999999999999%;&quot; class=&quot;responsive-object&quot;&gt;
    &lt;iframe width=&quot;200&quot; height=&quot;113&quot; src=&quot;https://www.youtube.com/embed/1_EOIsg89GA?feature=oembed&quot; frameborder=&quot;0&quot; allow=&quot;accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share&quot; referrerpolicy=&quot;strict-origin-when-cross-origin&quot; allowfullscreen title=&quot;Hotspot Demo&quot;&gt;&lt;/iframe&gt;
&lt;/div&gt;

&lt;/div&gt;




&lt;style&gt;
.overlay-embed-block .responsive-object {
    position: relative;
}

.overlay-embed-block .responsive-object iframe,
.overlay-embed-block .responsive-object object,
.overlay-embed-block .responsive-object embed {
    position: absolute;
    top: 0;
    left: 0;
    width: 100%;
    height: 100%;
}
&lt;/style&gt;


&lt;p&gt;The post &lt;a href=&quot;https://www.kdab.com/hotspot-v1-6-0-released/&quot;&gt;Hotspot v1.6.0 released&lt;/a&gt; appeared first on &lt;a href=&quot;https://www.kdab.com&quot;&gt;KDAB&lt;/a&gt;.&lt;/p&gt;</content:encoded><dc:creator>Editor Team</dc:creator><category>c++</category><category>desktop</category><category>linux</category><category>open source</category><category>performance</category><category>tools</category><category>ux/ui</category></item></channel></rss>