Thursday, 19 March 2026
Bind QML Values across an Arbitrary Number of Elements
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'll share my approach, in which I used a singleton C++ class serving as a message broker to bind properties across window instances.

Properties bound across multiple instances of a window
The App: Display to Light Panels
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.
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 "fill light" or a "scary spotlight", 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's a link to it at the end of this article. That's enough gaffer speak for today. Let's talk about the code.
The Code
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 Q_PROPERTY and updated via your control's signal handlers.
Message Broker Singleton
The message broker needs to be a singleton. That way there'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:
// 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 <QObject>
class InternalMessageBroker : public QObject
{
Q_OBJECT
// Hide regular constructor
private:
InternalMessageBroker() = default;
public:
// Disable copy constructor
InternalMessageBroker(const InternalMessageBroker& obj) = delete;
InternalMessageBroker& operator=(InternalMessageBroker const&) = delete;
static std::shared_ptr<InternalMessageBroker> instance()
{
static std::shared_ptr<InternalMessageBroker> sharedPtr{new InternalMessageBroker};
return sharedPtr;
}
// This is where all the signals would go
signals:
void broadcastAPropertyChange(int value, bool broadcast);
};Connecting Properties
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've created a class based on QObject 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 PropertiesModel:
// 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;
}
}
}
}
}
}Each property of our class is to be declared using a Q_PROPERTY macro with a getter, a setter, and a notifier. The getter and the notifier have nothing out of the ordinary...
// An ordinary getter
int PropertiesModel::aProperty()
{
return m_aProperty;
}// An ordinary notifier, form propertiesmodel.h
signals:
void aPropertyChanged();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.
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't worry about that in my code because broadcast is only to be used on calls to the private setter.
Upon the private setter being called by the public setter, it will emit the broker'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 PropertiesModel; 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.
// Private setter implementation for a property that's being broadcasted
void PropertiesModel::setAProperty(const int value, const bool broadcast)
{
if (broadcast)
emit m_broker.get()->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);
}To complete this loop as described, we need to connect the signal from the broker to the private setter of the PropertiesModel class whenever a new copy is instantiated. The best place to accomplish that is from the class' constructor, like so:
// 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(), &InternalMessageBroker::aPropertyChange,
this, &ScreenModel::setAProperty);
});
}QTimer to Delay Initialization
If you've been reading the blocks of code that accompany this article, you may be wondering "Why is there a singleShot QTimer there?" When the PropertiesModel class is instantiated via QML, any connected properties that have values assigned from QML code will trigger the Q_PROPERTY'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.
In the end, this is what the PropertyModel header would look like:
// propertiesmodel.h
#pragma once
#include "internalmessagebroker.hpp"
#include <QQmlEngine>
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<InternalMessageBroker> m_mb;
int m_aProperty;
};Conditional propagation
If a condition must be met for the propagated value to result in an update, then, in addition to value and broadcast, you should also broadcast any other values that are required to for such condition to be met. Pass those as arguments to the setter's and the broker's signal. Condition validation would then take place within the base case of the private setter. Here's a commented snippet of what that looks like on the Display to Light Panels app that this article was based on:
// 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 &screenName="s")
{
if (spread)
emit m_mb.get()->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();
}
}
Properties bound across instances of a window
Real World Example
For a real world application, you can read the code for Display to Light Panels, the app that inspired this article, at: https://github.com/Cuperino/Display-to-Light-Panels
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.
The post Bind QML Values across an Arbitrary Number of Elements appeared first on KDAB.
Tuesday, 17 March 2026
An amazing journey of 8 weeks full of code, new experiences, and interactions.
I had an amazing time at the Season of KDE 2026. All the guidance from my mentors, Benson Muite and Srisharan VS, really helped me work on Mankala and also helped me learn new skills.
So, lets summarize things a bit, what I was able to achieve during the span of these two months at KDE.
Complete GUI redesign
- I have made major changes in the MankalaNextGen GUI, introducing Kirigami to have different light and dark themes. Improved the design for different pages, MainMenu, Rules, About, Profile, and much more, where I worked to introduced better colors and geometry to the shapes, like buttons or display boxes.
- For the main Game interface, we made new boards and shells. I worked on integrating the logic for displaying the shells and fixing the Board based on the exact numbers shown from Mankala.
- I tried generating a board using Perlin noise, which can be later integrated into MankalaNextGen to design the boards more efficiently based on variants. Added new code to generate a calm background music and a wooden click sound to make it look more authentic.


Translations and Localization
I started learning about Lokalize, KDE’s own translation software. I had successfully made translations for Mankala Engine and MankalaNextGen in Tamil and Hindi using Lokalize.
Artworks and KDE review
- I have started working on the KDE review tasks and started with the fix to correct GitLab CI Build pipelines.
- In the last few weeks, I started learning about Krita and made cover images for the three variants Bohnenspiel, Oware, and Pallanguzhi using their cultural Mankala boards and traditional motifs.

The journey doesn’t end here. Season of KDE has been a much-needed experience to improve my commitment towards projects and open-source. I plan to work with Mankala Engine and the KDE Community much more and make many other contributions.
Thanks to my mentors, my fellow contributors, and to the entire community for helping me so far. 🚀
Monday, 16 March 2026
It’s been an eon since I’ve had anything meaningful to talk about in the world of Open Source, so maybe it’s time to post about some nonsense I’ve been working on!
I have a love/hate relationship with progressive web apps. My job these days is in a corporate environment, and there are some things you can’t escape. Outlook. Teams. Other web-based applications. Be it Chrome or Firefox, there’s just so many things PWAs don’t do well. No persistent system tray icons. No break-away from the browser. Odd menu entries. What’s worse is that the PWA standard itself is limited in a lot of ways, and it really does feel like an afterthought by every browser vendor. Also, you can’t exactly get PWAs easily listed in app stores.
There’s Linux-y solutions. Electron-based apps. But those are an incredible time investment to maintain, and there’s about a dozen apps they don’t cover.
My C++ fu has atrophied, having been replaced with Java as my daily working language, and I haven’t kept up with KDE frameworks and Qt… But this itch was driving me crazy, and I just wanted to see some proof-of-concept that the entire situation could be just a little bit better!
Here’s the moronic idea: We split the difference between browser-based PWAs and hyper-tailored Electron apps. We just have… A pure “PWA Browser.”
So, this weekend I decided to give this “Vibe Coding” thing a try, see if this idea is viable. After hacking with Claude over the weekend, I did manage to get some decent results… So, see for yourself!

The result is something I’m calling “Strand Apps”, because if you can’t have the whole web, maybe you can have a Strand of it.

How does it work? “Strand Apps” are basically just .desktop files that live in your .local/share/strand-apps folder. These simple manifests gives you the basics like name, description, home location, a few safe links the app should trust, default permissions, and a couple behaviors… Like so!

Ideally we simply define what would be “natural” for the application in a configuration file, and let the host provide exactly that. Would the app be persistent in the background? Sure. Turn on the tray icon. Does it need a toolbar? If not, turn it off. We get that “Near native” behavior, but without forcing people to maintain an entire electron container. Once we have that configuration then anyone with that config gets a top-tier experience.
The first time you launch a strand, you’ll get onboarded for that app. Pictures below is the Outlook 365 Strand…

It’s not pretty, but for now it lets you see what the app wants, and you can choose what you give it. There’s a lot to do with this to improve the onboarding experience, but it’s a start. Before I even get into it – no, basically none of these do anything. Don’t read too much into it, I just looked at what Chrome gives PWAs and ripped off that list.
Another thing on my todos is having it create launcher .desktop files in the menu as an option in the onboarding window. I have the ingredients there, just haven’t put em’ together.
That’s not to say it’s all wall-dressing. The applications each run in their own little silos, unable to read or see what the others are doing, the host system separating their browser persistence completely. I can be signed into an alternate Google account for my Gmail strand, and my main account on my YouTube strand. Each application can also manage its cache and storage separately, kinda like Android apps.

Apps using this don’t share settings. If you need to tweak one strand app, you don’t need to commit everywhere. This was a major problem I had with traditional PWAs, because I explicitly needed to disable hardware acceleration on one app (can’t remember why), but everything followed the parent Chrome installation. I remember I had Chromium installed just for that one specific PWA.
One of the key motivations I had was something small – tray icons and background persistence. Question: How do you get a fully functional Outlook app on Linux? I’m sure someone has a solution, but I never found a satisfactory one myself. Ultimately, I had a webview widget in my system tray purely for the icon and persistence, and a Chrome PWA for an actual app window that wasn’t locked to the corner of my screen. I’d use both interchangeably, but I was always annoyed by the solution – and resource usage.
This fixes that problem. That manifest snippet you saw has a “CloseToTray” value. Tells the app that it’s goanna persist. It does. These are the sorts of integration features I’m focused on. Here’s Gmail showing its proper indicator count in the tray:

Beyond the tray, I’ll be thinking about what other behavior settings an app might have, such as the config that removes the toolbar for “safe” apps, or for apps to specify if they’re single-window or multi-window by nature. What I really want is a solution where you can easily distribute apps that behave “as you expect”, and not like something with one foot in the browser.
Speaking of the tray… MPRIS works. Interestingly, navigation was borked in Chrome proper but somehow it “just works” with the strand. I didn’t even mean for that to happen.

Right now I have 3 Strand Apps; Gmail, Outlook, and YouTube Music. If I launch Strand without specifying a profile, it’s kind enough to give me a basic list of my installed apps. It also lets you launch the config for them before running, so if you break a web application profile you can recover. E.g. something has freaky WebGL that crashes the renderer.

The code is QT/KDE Frameworks. I didn’t wind up using Kirigami, ideally these apps won’t have much interface to them anyway, and I also wanted to use the fastest path from launch-to-application.
So… Perfect, right!?! NOTHING wrong with this setup? Push it up tomorrow?
This is where I get real about AI. Claude is pretty good, but it’s still an amateur chef in a professional kitchen. Even by my substandard C++ skills it’s apparent. At best I’d describe this work as wonderful scaffolding, but it’s still in need of a serious audit. I caught no less than 3 significant security risks it tried to give me, and it needs serious organization at minimum. Progress-wise none of the permissions are hooked up, and I want to dive into the PWA spec “proper” because I basically ignored it, and it might be able to streamline things. There’s also a lot of UX work to be done.
Beyond even that, there’s also the core design. The .desktop manifest format I designed is built around the concept that someone will make a bespoke configuration file for every app worth having. This has the strength that a properly configured strand will be a stellar out-of-box experience, and with YouTube Music as my dog food it almost feels nicer than the Electron version… But how do we distribute these weird files? Does someone even want to distribute them? Is my silly little experiment giving people reading this headaches because infrastructure and distribution is always the bigger problem? Who makes them? Is my flagrant disregard of the .desktop file spec going to cause problems? Should I brace myself for the lynching that using AI has earned me? No matter what, the one thing I know is that, somehow, these can be distributed.
This is something I’m going to futz with for a while. I figure I’ll do a serious code review next weekend, maybe re-org it, then get it into a git repo somewhere when I’m satisfied it won’t eat anyone’s cat.
Sunday, 15 March 2026
KDE Gears 26.04 hit beta phase just a few days ago, so it is time to give an update on the new features in KJournald.
For 26.04, the main release goal was to have a natural integration of systemd user services and system services in the log view workflow. This means:
- When opening a journald database on a system, from now on per default both the system and user service log records are opened and both will be displayed in a joint view. — If you do not like this, got to “Current Journal” -> “Limit Accessed Logs” and choose your preference. That configuration then is also stored persistently over several boots.

Revisiting the overall UI with this feature in mind, we need few more changes to really make it fly:
- User units often are templated systemd services. This means, there is a single service definition but multiple instances of the service template with different parameters can be created. Using the traditional way to list every unique service in the filter criteria view (left side view) makes the filtering hard to use due to the number of services. So, as a new (opt-out; enabled per default) feature, templated services are grouped. So, for example, all my “app-org.kde.dolphin@[…].service” services can be enabled/disabled at once in the filter view. In the service name hover-info in the log view however, they still are displayed as full service names.
- Typically, due to user units being user workflow specific, there are a lot of systemd services that are not started in every boot, because a user can control their startup individually. For this reason, services and executables are now being pre-filter according to the selected boot. Log priorities are not (yet) taken into account for this pre-filtering, but at least only useful filter entries are shown, which should be a major usability gain on its own.
If you interested in the features, try out the KDE Gears beta release or just install the latest nightly build of org.kde.kjournaldbrowser from the KDE Flatpak nightly CI.
Saturday, 14 March 2026
Another month has gone by since the last time I wrote about KDE Linux, KDE’s upcoming new operating system. The project hasn’t stood still! Here’s an assortment of what’s gone on recently:
Real sizes for system updates
Aleix Pol Gonzalez and Harald Sitter built the machinery to allow update sizes to be calculated for delta updates. This means the sizes listed in Discover are no longer “Unknown”. Instead, you’ll see a real number:

Better tools for extending the OS
Lasath Fernando started building out the new Kapsule system, which is a tool for installing software in long-lived containers with great integration with Konsole and the rest of the OS. Before this, we experimented multiple options for experts to extend the system — including Homebrew, Distrobox, and Nix — but none really hit the sweet spot. They were too limited, too dangerous, too complex, too ephemeral, or too something else.
Kapsule deeply integrates with Konsole, which makes sense because a terminal window is a major interface for touching or extending the system in this way. Integrations with Kate and Discover are planned, too. In other words, we want to go all in on this promising new technology for the “extending the system” story for experts and software developers.
You can read more about Kapsule in these blog posts.
Moving to better infrastructure
Harald did a lot of work to upload KDE Linux images to a better location, in preparation for far higher server loads in the future as the OS gains users and rolls out a user-focused edition.
Pre-installed backup system
I pre-installed KDE’s Kup backup system, which is really quite nice! Basic documentation can be found here.
APFS filesystem support
Hadi Chokr turned on support for reading from and writing to disks formatted with Apple’s APFS filesystem.
Safer Homebrew, ydotool, and AMD GPUs
renner03 put in place a safety mechanism that prevents Homebrew packages from breaking the system in case you install Homeberew and any of its packages would otherwise conflict with system files. Now, the Homebrew packages break instead.
Note that we still don’t recommend Homebrew. But now you can use it without endangering the rest of the system.
I made the ydotool automation tool safer; now it’s an off-by-by-default system service you need to authenticate to turn on.
I also put in place a workaround for total system freezes affecting AMD GPU users. This stubborn AMD driver issue has been unfixed for months, so a workaround here is appropriate.
More languages in pre-installed Flatpak apps
Guilherme Marçal Silva fixed an issue that made the pre-installed Flatpak apps only usable in English.
Longer time for installation
Jonar Harer bumped the installation timeout from 30 minutes to 1 hour to account for slower devices.
Outstanding issues
KDE Linux is still an alpha product with lots of bugs and rough edges. We had our second dev call yesterday and discussed the road to a beta release, which will include user builds. We’re going to be focusing on a number of high priority issues and will consider the other beta-milestoned issues to be done on an “if we can” basis.
Get Involved!
Progress on KDE Linux is steady but nonetheless rather slow. Help is greatly appreciated. In addition to installing it and reporting issues, there are lots of development task that need doing:
We feel how dependencies can hurt
There is a lot of talk about digital sovereignty. Being able to act as a state or as a company is obviously important. But there are real dependencies, and given the current geopolitical dynamics, there are real risks. Unfortunately, there are no easy answers. Digital sovereignty matters, but so do stability, efficiency, and innovation. Fortunately, there are options and some good examples of how to deal with it. I collected some material in an awesome list on digital sovereignty.
While it is complex at the state level, it is merely complicated at the personal level. Reaching something like personal digital sovereignty is possible. If you are informed about the technical landscape, you probably already have a good intuition about it. You feel the pain of having to stop using a service because the provider decided to discontinue it without you having a say. You can decide whether it feels right to upload your personal diary to a server in a jurisdiction you do not control.
Free Software provides a path
There is a clear path to personal digital sovereignty. The goal is nicely expressed in KDE's mission: "A world in which everyone has control over their digital life and enjoys freedom and privacy." The path is provided by Free Software. The freedoms to use, study, share, and improve give you exactly what you need to be in control.
For software you run yourself, this works well. Running Free Software on your personal computer gives you control. It feels good. It becomes more complicated when you use services you do not and cannot run yourself. The software freedoms do not transfer easily. There are a lot of services, which are mostly based on Free Software, but only the service providers enjoy the freedoms, not their users. I have written about this before when working on my Fair Web Services project.
A good testament to personal digital sovereignty is the Blue Angel for software. Its focus on resource and energy efficiency is one side of responsible software use. Maybe even more important is its emphasis on user autonomy: being able to use software without ads being forced on you, being able to choose what to install, and having transparency about what you run. These are the ingredients of personal digital sovereignty.
Finding the balance
Freedom is one side, but convenience is another. Sometimes it is easier to just use something a vendor has invested heavily in providing, even if you pay with your data and some independence. It is also a question of where you spend your time: do you build something for yourself, or do you use something that already exists? And sometimes it is about the limits what you can do yourself. Powerful tools can give you leverage so you can focus on your actual mission.
So it is also about compromise. One very important aspect for me is that I am still able to choose. That is the core of personal digital sovereignty. Sovereignty does not mean doing everything yourself. It means preserving the ability to leave, even if you choose not to.
Federated services make it easy to migrate. For git, for example, it does not matter so much where the server is or who runs it, because switching is as simple as changing the remote. For a proprietary note-taking service, this looks different. You may need special exports, format conversions, and you might lose functionality because it is not based on open standards. Choose your dependencies wisely.
It is important to remember that dependencies are not bad per se. We know this from Free Software. We know what it feels like to stand on the shoulders of giants. We rely on the collective strength of a global community. It is not about rejecting all dependencies or doing everything on your own. It is about creating alternatives and shaping an ecosystem based on openness, so that we can choose and act on our own terms.
My personal stack
I am quite happy with my personal stack, which gives me the control I need. My 12-year-old desktop runs Linux and KDE. I pay to host my own email, Nextcloud, and git services. One project I particularly like is GitJournal, which gives me control over my note-taking across all my devices. This covers the core of my computing needs, with my family, my friends, and what I decide to keep private.
To stay connected to the wider world, there is no way around being present on large networks. GitHub and LinkedIn are the compromises that give me reach without requiring me to abandon all my principles. I would not publish my writing only on LinkedIn, though, because I want to own what I produce.
AI is a difficult question right now. It is easy to switch between services, and with rapid development it is changing quickly what the best choice is. And it can provide tremendous leverage. So it remains an evolving compromise. An ideal future would offer open models powerful enough to serve your needs and that you can run locally.
Building digital sovereignty
On a personal level, you can decide for yourself. There are limitations, and you will have to build on the environment available to you. But there are alternatives, and you can choose to build your personal digital sovereignty.
At the corporate and state level, it is more difficult. The systems are more intertwined, but the pain of dependencies you cannot control and the risks of others making decisions for you are just as real. Alternatives exist there as well, often the same ones available on a personal level. It can be worth taking bold decisions.
Digital sovereignty at the state level is about national security. At the personal level, it is about personal freedom. Free Software provides a powerful path to maintaining control over our digital lives.
I am not arguing for tools. I am arguing for agency.



