Jan Grulich

Qt theming in Fedora Workstation

We have been working on and using custom Qt theming in Fedora Workstation for many years now. By custom Qt theming, I’m talking about the QGnomePlatform and Adwaita-qt projects. If you haven’t heard of them, you can read my recent blog post explaining what they are. While these projects are in some ways better than what Qt upstream has to offer, there were also drawbacks/issues and that’s why I decided to make a final decision and discontinue both projects. The issues are explained in the aforementioned blog post, but one of the main drawbacks is that we are in this development alone and not working directly in the upstream makes it less attractive for other contributors. It’s also not used by default anywhere other than Fedora, so it’s not properly tested by other developers working on Qt applications using different distributions. These reasons led me to submit a Fedora 39 feature to remove our custom Qt theming in Fedora Workstation in favor of Qt’s defaults. The only problem is that if we just go with Qt’s default, we would go backwards a bit. This is because upstream Qt does not provide any decent client-side window decorations (problem #1), and the QGtkTheme in Qt5 (QGnomePlatform equivalent) is a bit behind its Qt6 version with many improvements and integration goodies (problem #2) recently made by Axel Spoerl of the Qt Group, whom I met during this year’s KDE Akademy.

Solution to problem #1

QGnomePlatform used to be our solution to this problem, as QGnomePlatform implemented it’s own version of the QWaylandAbstractDecoration plugin. This was a GTK 3-like decoration plugin that used Adwaita-qt for button rendering and QGnomePlatform bits (e.g. GSettings configuration) to get the titlebar layout. Since we are going to remove QGnomePlatform, we needed an alternative. So I started working on the QAdwaitaDecorations project. This is supposed to be a an intermediate step as I would like to have a proper GNOME/Gtk decorations directly in Qt upstream, but since I was in a hurry to get everything done in time for Fedora 39, we have this for now. QAdwaitaDecorations plugin is based on the decorations we have in QGnomePlatform, but there is no dependency on GTK or Glib (e.g. GSettings) or on Adwaita-qt. We use xdg-desktop-portal to get the titlebar layout and do our own drawing instead. This decoration plugin should also have now a GTK 4-like style so the buttons and colors of the decorations are different.

Below is a screenshot of Wireshark (Qt6) using QAdwaitaDecorations plugin + QGtkTheme + Fusion:

Solution to problem #2

Since Qt5 is no longer actively developed, the only possible solution is to backport all QGtkTheme improvements from Qt6, so I did that + modified some of those changes to avoid breaking binary compatibility. This results in about ~15 related backports to Qt5 so far, and it seems to work pretty well. I also made sure that Fedora 38 and older will still use QGnomePlatform by default, so we don’t change the behavior for existing users. Also, a small change to our QtWayland package was needed to make it use the new decoration plugin by default.

Future plans

As mentioned, I would really like to have everything directly in Qt upstream (talking about QAdwaitaDecorations). That way we get other contributions and thus fixes/improvements for free and a lot more users. Another thing is that QGnomePlatform supports things that are not yet supported/implemented in QGtkTheme, like support for xdg-desktop-portal instead of just relying on GSettings. Not to mention that GTK 4 has been around for a while, and both QGnomePlatform and QGtkTheme are still GTK 3 based. I will definitely try to make some of these things happen for Fedora 40, but knowing myself, it’s better not to make any promises, as things usually don’t go according to plan.

Explained: QGnomePlatform and Adwaita-qt

I decided to write this post to explain everything I can about these two projects. There have been discussions and people demanding these projects should not be used by default in Fedora. As part of this, some issues were raised and it might not be clear which component might be responsible for what. I ended up constantly defending these projects in many discussions and ended up being exhausted by doing so over and over, so take this as an explainer to shed some light.

Brief introduction to QGnomePlatform and Adwaita-qt

To give you some context before I go into details, you can think about Adwaita-qt as the UI representation and QGnomePlatform as the integration between GNOME and Qt. QGnomePlatform applies your GNOME configuration and behavior to Qt apps, together with some integration bits, like dialogs or client-side decorations. Adwaita-qt is responsible for the style of the app itself, including the style of all visible parts (widgets/buttons).

QGnomePlatform

What is QGnomePlatform?

QGnomePlatform is a Qt Platform Theme (part of Qt Platform Abstraction API), where such a plugin is responsible for the app integration into the desktop environment. It is designed to provide integration between Qt apps and the GNOME platform. To explain in an example. Without any platform integration, Qt apps running on GNOME would use default styling and configuration so your fonts, icon theme, dialogs would not fit into the desktop. Also in the case of QGnomePlatform you would not have GNOME-like client-side decorations.

What QGnomePlatform provides?

QGnomePlatform provides the following integration for Qt apps running in GNOME:

  • Font configuration *
  • Icon theme *
  • Cursor size and cursor theme *
  • Static hints (like double-click time, long-press time etc.) *
  • Dialogs:
    • File dialog (both using GTK3 * and native dialog using xdg-desktop-portal)
    • Font dialog using GTK3 *
    • Color dialog using GTK3 *
  • Client-side decorations
  • Support for Settings portal from xdg-desktop-portal settings
    • Unlike the built-in GTK3 theme that can get everything only from GSettings
    • Brings support for light/dark theme switching introduced in GNOME 42
  • Use Adwaita-qt theme by default (elephant in the room) and Adwaita color palette
    • Also provides support for additional themes, like Kvantum
  • Support for Cinnamon desktop

* these can also be provided by built-in GTK3 platform theme in Qt itself (just for comparison what QGnomePlatform does extra)

Issues QGnomePlatform gets wrongly blamed for

Client-side decorations

As stated above, QGnomePlatform provides an implementation of CSD. It’s actually the only Qt CSD implementation I’m aware of, excluding the reference implementation provided by QtWayland, named Bradient. Below is a screenshot comparing QGnomePlatform (left) and Bradient (right).

I saw many times people complaining about missing shadows support and resizing issues. The truth is that officially there was no proper shadows support in the Qt API until I introduced it with Qt 6.2. That’s the reason we don’t have it for Qt5, unless you are a Fedora user, where this support has been backported and enabled in QGnomePlatform build. I also fixed all kinds of CSD related issues in QtWayland.ven though Qt has proper support for shadows in Qt6 now, the reference implementation doesn’t use them.

Misplaced popups/menus

This has nothing to do with Qt platform theme or CSD implementation, because it was actually a bug in QtWayland. Unfortunately this fix is only in Qt 6 and cannot be backported officially to Qt 5 as it would break KDE Plasma. I managed to at least patch QtWayland in Fedora and Flatpak KDE runtime, where I modified this patch to not affect KDE Plasma at all.

What can QGnomePlatform be blamed for?

Forcing Adwaita-qt color palette

QGnomePlatform sets Adwaita-qt color palette to each Qt app so applications can use QPalette API to get access to colors used in the style itself. This can be for example useful when an app creates custom widgets that would not get styled by the QStyle itself. This creates a problem for KDE applications using the KColorScheme API. 

Examples of this issue:

The reason is that KColorScheme and QPalette are out of sync and there might be color roles that are in KColorScheme, but not in QPalette. If an app requests a color from KColorScheme that’s not in QPalette, KColorScheme will default to Breeze style and a color that’s not going to fit the Adwaita-qt style will be provided, causing the app to mix light and dark colors.

Luckily, we have identified a workaround that can be done in QGnomePlatform to avoid this issue. Here is the QGnomePlatform bug with more details.

I think this is the most visible and user-facing issue we currently have and get blamed for so I would like to fix this as soon as possible.

Adwaita-qt

Adwaita-qt is a Qt style for widgets. Qt style is again part of Qt Platform Abstraction. You can think of it as a theme for your application. It’s what changes the visualization of your widgets like buttons, checkboxes etc. and it’s the only thing that changes the appearance of the application itself. For comparison, the screenshot below is Adwaita-qt (light) and the second one is the Qt’s default Fusion style used on Linux used without any QGnomePlatform influence so basically what you would get by default.

I can also add that Adwaita-qt supports HighContrast variants, which are useful for visually impaired people.

What issues can Adwaita-qt be blamed for?

I already mentioned the color mismatch issue which is not really Adwaita-qt’s fault. There are of course issues in Adwaita-qt itself and it’s far from being perfect. The whole style needs a complete overhaul, because the last one was done in 2019 and the Adwaita theme changed a lot recently with GTK4. Another issue is that while the majority of common widgets are styled just fine, there are still some widgets that are rarely used and might have issues with this style.

Another issue is that  apps that customize standard widgets (e.g. through CSS), might get into trouble. Below is a screenshot of Wireshark (pure Qt app) compared to Breeze in KDE.

Another example can be seen in the Black Chocobo app, which uses some customization:

 
Below is Black Chocobo using Fusion style.

Should QGnomePlatform get removed/replaced?

Definitely not. It has many benefits and extras compared to Qt’s default platform and most importantly gives you CSD support. Once I fix the color mismatch issue, there shouldn’t be anything users should complain about. Obviously, this would not be an issue when Breeze is used instead of Adwaita-qt, but still an issue when Fusion is used so it would still need to be addressed. You can see a screenshot below showing Fusion style used in combination with GNOME set to dark theme:

Should Adwaita-qt get removed/replaced?

Maybe. It depends on the alternatives. Obviously using Breeze would get rid of all the widget issues one might experience with Adwaita-qt, however, it is problematic to ship it by default due to bringing dependencies on KDE Frameworks and Plasma breeze style. With the default Fusion style you will also get many widget issues fixed, but you still need to set the color palette through QGnomePlatform in case you want Fusion to be “dark” and fit into the desktop, otherwise you will always end up using the default “light” variant no matter what configuration you set in GNOME.

Conclusion

I hope that this post is useful for those observing issues with Qt apps under GNOME, and will help them to understand which component is responsible for what, as well as the issues involved. In case you are interested and would like to either contribute a patch or report an issue, here are links to QGnomePlatform and Adwaita-qt repositories.

How to use libportal/libportal-qt

There was a blog post from Peter Hutterer about Flatpak portals posted few months back. Peter explained what are portals and how do they work. Portals are used mostly because of security and sandbox/Wayland restrictions. Many times your only way to get access outside (opening a file, sending a notification, sharing a screen, etc.) is to use a portal. For most use-cases applications or developers don’t need to care about them as their support is usually implemented in libraries they use. For example Qt and GTK use portals internally so apps can use still the same APIs as before and they don’t need to worry about their apps not working in sandboxed environments. BUT there are still scenarios where libraries have unsufficient or none portal support, or a different options are desired so what are the options in this case if you still need to use portals?

  1. Do everything yourself, which means you will implement all the DBus calls and handling yourself.
  2. Use a library. Most logic choice would be libportal, but there is also a project called ASHPD for Rust users.

What is libportal and libportal-qt?

The libportal library provides GIO-style async APIs for Flatpak portals. It hides all the DBus complexity users would face in case of using portals directly and provides a user-friendly library instead. You might think that the libportal-qt is the same thing, just with Qt-style APIs, but the idea behind it is that each toolkit (Gtk3, Gtk4, Qt5, Qt6) has a different way to get a window handle which is needed to associate portal dialogs with the app that invoked them. So libportal-qt just provides a way to get a XdpParent object from a QWindow. As a C++/Qt developer I don’t mind using C/Glib APIs and I used it many times, but there is still one speciality I fail to use everytime, my friend GVariant. Some of the portal APIs in libportal expects a GVariant for all the complex structures, for example to specify a filter option for OpenFile() call from the fillechooser portal, you have to build a very complex GVariant based on the DBus specification.

Remember I told you libportal-qt doesn’t offer Qt-style APIs? This is not necessarily true, because I implemented all the complex structures you will have to pass in most of the portals and implemented functions that will return them as GVariants so you don’t need to get in touch with GVariants at all.

How to use libportal-qt?

First of all, all libportal flavours have pkgconfig file installed so it’s easy to use them from any build system and you just need to search for libportal-qt5 (we don’t have -qt6 version yet).

And how does the code look like? For example let’s say you want to open an image:

// Creates a filter rule, this can be a Mimetype or Pattern.
XdpQt::FileChooserFilterRule rule;
rule.type = XdpQt::FileChooserFilterRuleType::Mimetype;
rule.rule = QStringLiteral("image/jpeg");

// Create a filter with our rules, we will then pass it to OpenFile() call as GVariant.
XdpQt::FileChooserFilter filter;
filter.label = QStringLiteral("Images");
filter.rules << rule;

// Create a GVariant from our filter. This will result into variant in form of:
// "[('Images', [(1, 'image/jpeg')])]"
g_autoptr(GVariant) filterVariant = XdpQt::filechooserFiltersToGVariant({filter});

// Get XdpParent to associate this call (portal dialog) with our window.
XdpParent *parent = xdp_parent_new_qt(m_mainWindow->windowHandle());

// Finally open a file. XdpQt::globalPortalObject() is another convenient function 
// that creates a global instance of XdpPortal object so you don't need to take care
// of creating it yourself. For some of the arguments we just pass a nullptr to don't 
// specify them.
xdp_portal_open_file(XdpQt::globalPortalObject() /*XdpPortal object*/,
                                  parent /*XdpParent object*/, "Title", filterVariant /*filters*/,
                                  nullptr /*current_filter*/, nullptr /*choices*/, 
                                  XDP_OPEN_FILE_FLAG_NONE /*flags*/, nullptr /*cancellable*/, 
                                  openedFile /*callback*/, this /*data*/);
xdp_parent_free(parent);

// Then the callback would look like this, eg.
static void openedFile(GObject *object, GAsyncResult *result, gpointer data) {
    g_autoptr(GError) error;
    g_autoptr(GVariant) ret = 
        xdp_portal_open_file_finish(XdpQt::globalPortalObject(), result, &error);

    if (ret) {
        // Another convenient function that will get you uris and choices from 
        // GVariant returned by xdp_portal_open_file() call.
        XdpQt::FileChooserResult result = filechooserResultFromGVariant(ret);
        
        // Do whatever you want to do with the result. Here we just print opened selected files.
        qDebug() << result.uris;
    }
}

As you can see, no GVariant got hurt and you can easily open a file without any GVariant knowledge. Besides FileChooser portal helpers, we also have Notification portal helpers, because serializing icons and buttons is also something that is not trivial. For the rest of the portals you either don’t need to use complex GVariants so you can use them easily without helper functions same way as shown above, or some portals like ScreenCast or RemoteDesktop are not used that often and we don’t have helper functions for those just yet.

I hope you can find this helpful in case you want to join this world. The libportal project is hosted on GitHub in case you want to try it just now, because this is still not part of any stable release (will be in libportal 0.6), or report a bug or just look at my GVariant helpers to see what I spare you of.

HighContrast variants for Adwaita-qt

In the past we used to have a completely different project to cover HighContrast variants of GTK Adwaita theme. This was all implemented as Highcontrast-qt, a project nobody has touched for 6 years. You can imagine how it looks like these days when you compare it to what we have now. I think even GTK variant of HighContrast was a completely separate theme back then, while today days it’s just Adwaita with a different set of colors.

Since GTK made the new HighContrast theme with just few modifications to the original Adwaita theme, I decided to use same approach and have Adwaita-qt to provide all four variants as well (Adwaita, Adwaita-dark, HighContrast and HighContrastInverse). While this looks like a simple thing to do, as you just need to add additional color palette, it was a pain to do it in Adwaita-qt. The reason is that Adwaita-qt is full of hardcoded color definitions, where all of them were randomly taken from GTK Adwaita stylesheets. Everytime something changed in GTK Adwaita, we would have to manually pick the change and replace the changed color value on our side. This was not really sustainable, especially when I wanted to have four different variants.

To improve this situation and make my life easier, I decided to bundle GTK stylesheets for Adwaita theme, have it processed and write a simple parser to make everything automated. And I did exactly that. I included the stylesheets and wrote some definitions myself so I can let them processed with sassc (GTK uses SASS for theming) and have all my definitions substituted for simple parsing. I no longer have to pick all base colors manually, all of them are being parsed for all four variants and same goes for basic styling of Buttons, CheckBoxes and Radio Buttons, where each of them have all kind of possible states (active, hovered, checked etc.) and use not only a simple color, but also gradients. You can imagine how hard it was to hardcode all values for each state. The parser I wrote is really basic and simple with use of regular expressions as the code I’m trying to process is not that complex.

The code I try to process is either a simple definition:

@define-color base_color #ffffff;

Or widget definition in this form:

button:checked { color: #2e3436; border-color: #cdc7c2; background-image: image(#dad6d2); box-shadow: none; }

The result is:

Adwaita-qt: Adwaita variant
Adwaita-qt: HighContrast variant

I think this is a big step forward for Adwaita-qt and it will allow me to more quicky respond on changes happening in GTK Adwaita/HighContrast theme. I can also imagine this being extended in the future to support some additional variants, like modified Adwaita theme you can find in Ubuntu (at least if the stylesheet is similar enough). As mentioned, Adwaita-qt now supports four variants and they should be on par with GTK 4.4, at least when it comes to colors and style for most used widgets, because there are still places in Adwaita-qt which need some extra work. Anyway, this all is now released as Adwaita-qt 1.4.0 and I will be updating Flathub and Fedora builds to it soon.

Cute Qt applications in Fedora Workstation

Fedora Workstation is all about Gnome and it has been since the beginning, but that doesn’t mean we don’t care about Qt applications, the opposite is true. Many users use Qt applications, even on Gnome, mainly because many KDE/Qt applications don’t have adequate replacement written in Gtk or they are just used to them and don’t really have reason to switch to another one.

For Qt integration, there is some sort of Gnome support in Qt itself, which includes a platform theme reading Gnome configuration, like fonts and icons. This platform theme also provides native file dialogs, but don’t expect native look of Qt applications. There used to be a gtk2 style, which used gtk calls directly to render natively looking Qt widgets, but it was moved from qtbase to qt5-styleplugins, because it cannot be used today in combination with gtk3.

For reasons mentioned above, we have been working on a Qt style to make Qt applications look natively in Gnome. This style is named adwaita-qt and from the name you can guess that it makes Qt applications look like Gtk applications with Adwaita style. Adwaita-qt is actually not a new project, it’s been there for years and it was developed by Martin Bříza. Unfortunately, Martin left Red Hat long time ago and since then a new version of Gnome’s Adwaita was released, completely changing colors and made the Adwaita theme look more modern. Being the one who takes care of these things nowadays, I started slowly updating adwaita-qt to make it look like the current Gnome Adwaita theme and voilà, a new version was released after 3 months of intermittent work. You can see the results here:

Isn’t it beatiful? The theme is far from being perfect and there will definitely be still some minor issues, but writting a Qt style is far from being an easy task as the QStyle class is quite complex. If you find any issue, you can open a bug and I will try to fix it. You can also send me patches if you decide to fix something yourself (I will be happy for that). The repository is hosted on GitHub under FedoraQt/adwaita-qt.

And of course it was a lie, the screenshots above are the old version of adwaita-qt (for comparison), the new ones are actually here:

I hope you like it more now :).

Gnome integration for Qt based applications in Flatpak

Following blog post from Patrick Griffis about new themes support in Flatpak, we started working on supporting this new feature too. Currently wherever you start a Qt application, it would always look like a KDE application or something would be missing, like icons so you would end up with bad experience and mixed feelings. This is going to change now as we now support Gnome in form of icons, widget style and Qt platform theme and with this, when you run a Qt application in Gnome, it will look definitely better and more natively than before. We packaged regular adwaita icons which are used by default in Gnome as extension of freedesktop runtime. For widget style we use adwaita-qt style, which is a Qt style attempting to look like Gtk’s adwaita and the most important part putting this all together is QGnomePlatform, a Qt platform theme which reads your Gnome configuration and applies it to running Qt applications. QGnomePlatform also enforces Qt apps to use adwaita icons and adwaita-qt style by default so that’s another reason why it is important. Both adwaita-qt and QGnomePlatform projects are by the way authored by Martin Bříza, a collegue of mine from Red Hat so if you meet him in person somewhere buy him a beer for that he cares about Qt integration in Gnome :). Now coming to a question how to install this and make it work. Basically all you need to do is install following extensions and you shold be done:

flatpak install kderuntime org.freedesktop.Platform.Icontheme.Adwaita
flatpak install kderuntime org.kde.KStyle.Adwaita
flatpak install kderuntime org.kde.PlatformTheme.QGnomePlatform

Your Qt apps running in flatpak should then automatically pick up all of these extensions without any further modification, same way it does automatically when you run it outside the sandbox. Simply done!. I’m also aware that there are more Gtk themes, like adwaita-dark or high-contrast, both are also available in form of Qt style and we will probably package them later, but at this point it is mostly proof of concept that this can be done and works nicely. You can follow our wiki page if you want more information about runtimes, repository with applications and so on and from me it’s all for now.

Btw. below you can see okular running in flatpak and using adwaita-qt style with adwaita icons.

Scroll To Top