How platform integration in Qt/KDE apps works
There has been some recent discussions about how KDE applications (or Qt apps in general) should look and feel like outside of the Plasma desktop, particularly in a GNOME environment.
During this discussion I noticed two major disconnects between the involved parties. One of them is technical in nature, where (understandably) not everyone involved has deep knowledge about how Qt and KDE apps work. The other one is cultural in nature, where there’s opposing views about who gets to decide how an application should look and feel like on a given platform.
I can’t do much about the cultural issue, but I can help the conversation by giving some much needed overview of how any of this works on a technical level. Everyone being on the same page technically could help foster a more productive conversation about this complex topic.
First of all it’s important to note that Qt to its core is an abstraction across various plaforms (most important here are Linux, Windows, and macOS, but also to some degree Android and iOS). Whenever possible Qt tries to use the platform’s native facilities to do anything, whether that’s rendering, file dialogs, widget styles etc. This becomes somewhat messy when you consider that “Linux” isn’t exactly a single, well-defined “platform”. Qt does usually have non-native fallbacks for things like file dialogs and widget styles, but they aren’t necessarily something you want a user to have to see. It’s also important to mention that Qt has two somewhat competing ways of defining UIs, the traditional QtWidgets, and the more recent QtQuick/QML.
There are several somewhat independent pieces involved in how a Qt application looks and feels. Jan Grulich already talked about some of them in the context of GNOME and QGnomePlatform, but there are also things specific to KDE applications that aren’t mentioned.
The first piece is the “Qt Platform Theme (QPT)”. Despite the name it doesn’t have much to do with the visual style. It is responsible for applying settings from the platforms. This for example includes font settings, the double click interval, or whether a file should be openend on single or double click. It also defines how standard dialogs look like, most importantly the file picker dialog, but also dialogs like a color picker. Third, it defines the color palette (QPalette) the application is using. More on that later. Qt itself ships platform themes for non-Linux platforms as well as somewhat generic Linux platform themes for GNOME and Plasma. Notable out-of-tree plugin exist, like plasma-integration which you are using right now if you are on Plasma, the aforementioned QGnomePlatform targeted towards GNOME (and to some degree similar environments), and qt5ct, which isn’t aligned to a specific environment and provides generic control over platformtheme things.
The second, and perhaps most well-known, knob is the widgets style (also called QStyle). It controls the majority of the appearance of a QtWidgets application. Well-known examples include Breeze (the current Plasma default), Oxygen (the KDE4-default), adwaita-qt, as well as built-in styles for Windows/macOS. Qt also comes with a built-in Fusion style. QStyles are implemented using C++ plugins. Whenever the app needs to render some piece of UI, e.g. a button, it defers that to the style plugin. Some style, like e.g. Windows then use platform native APIs to render widgets, others like Breeze draw the widgets from scratch. Application developers can also include custom styles for complete control over the appearance.
The third important concept is QPalette. A QPalette is a set of colors used to draw UI elements. The palette is defined by the platform theme(!). For example Plasma uses this to apply the color scheme set in System Settings. QGnomePlatform uses it to apply Adwaita-like colors. The selected QStyle may (or may not!) use this palette when drawing controls. The application developer can also manually query colors from the palette for drawing custom widgets while still respecting the platform’s wanted colors. A platform theme may only offer a single palette this way, or include light and dark variants, or allow the user to configure arbitrary color sets (like we do on Plasma). It is also possible for application developers to override the system-provided palette, for example to offer an in-app dark mode switch.
For applications using QML there is another relevant component: The Qt Quick Controls 2 Style. For reasons I’m not going to go into QtQuick Controls don’t use QStyle for their styling. Instead they come with their own stying system, which is itself based on QML. In Qt5 QML apps only have a very basic and broken default theme that should never be used. In Qt6 they use Fusion by default.
These are the relevant knobs every Qt app has. Some app developers choose to use them to control the appearance of their apps themselves, but many others leave it to the environment to apply a suitable look and feel. Furthermore, there are some relevant KDE-additions to this that are important to understand.
One such addition is KColorScheme. You can think of KColorScheme as a superset of QPalette, i.e. it provides additonal color roles and thus finer-grained control over colors. When changing the Colors setting in Plasma’s System Settings you are picking a color scheme. This gets applied to QPalette via the plasma-integration QPT, but can also be queried directly by the application developer for custom painting. Contrary to QPalette a KColorScheme is not porgrammatically filled based on plaform values (that happens only on Plasma), but it is a static, textual list of colors. Here we have the first problem for running KDE applications under e.g. GNOME. When running a KDE app on GNOME QGnomePlatform will apply Adwaita colors using QPalette. However, this does not affect colors the application directly pulls from KColorScheme, which unless explicitly configured has a default that resembles Breeze. This means we get mixtures of two different color sets, giving unpleasant results. This is especially noticeable when using a dark system theme combined with the light default colors from KColorScheme.
How do we solve this? Well, I’ve been banging my head against that problem for a while. Short of removing the concept of KColorScheme entirely I see two realistic options, not necessarily mutually exclusive. QGnomePlatform could create a KColorScheme definition with Adwaita-like colors and apply that to the application. If exuted correctly it would likely give very good results, but obviously only on platforms that use QGnomePlatform. The other option would be to programmatically derive a KColorScheme definition from a QPalette, which is likely much harder because KColorScheme is a superset of QPalette, but it would be a generic solution for all platforms.
The second noteworthy thing for KDE applications affects QML apps in particular. I’ve mentioned that QML has a separate theming system compared to QtWidgets. Because maintaining two style definitions for different systems is no joy KDE maintains a “hack” around this. qqc2-desktop-style implements a Qt Quick Controls style that fetches style information from a QStyle, which means all the existing QStyles out there keep working for QML apps. It works amazingly well, until it doesn’t. One of the shortcomings of this approach is that qqc2-desktop-style internally heavily relies on KColorScheme, which makes the aforementioned mismatch between QPalette and KColorScheme much more prominent. Possible solutions are the same as mentioned before.
I hope this gives some much needed overview over technology and terminology of involved components and helps with a productive way forward with addressing the problems we have. You are welcome to join this discussion. There’s some other relevant things to talk about, like icon loading, theming, and rendering, but that’s for another day.