Platform APIs in Qt 6

While Qt solves many of the typical tasks of writing an application, there are always corner cases that Qt can not cover, or where it makes more sense to build a feature on top of the platform specific APIs, or another toolkit. One of the tasks we wanted to address for Qt 6 was to clean up and coordinate the various mechanisms we had for accessing platform-specific functionality.

We'll now  go through the result of this work in Qt 6. The full documentation is available in the documentation snapshots, as part of the new Platform Integration section.

Type Conversions

Many of Qt's basic data types, such as QString, QPoint, or QImage, provide conversions from and to the native equivalent types.

For example, to get the current user's username on Apple platforms:

NSProcessInfo *processInfo = NSProcessInfo.processInfo;QString userName = QString::fromNSString(processInfo.userName)

For a complete list of all type conversions, see the Type Conversions overview.

Window Embedding

Windows created by the underlying platform APIs may be used as both parent containers for Qt windows, or embedded into Qt windows as child windows.

The former is useful if the application is mainly written using the native platform APIs, but where parts of the application use Qt, for example to draw a specialized UI. To embed Qt into the window hierarchy of the native application, use QWindow::winId() to get the native handle for the Qt window, and then use the native APIs to re-parent the window into the native UI.

The latter is useful if the native platform, or another toolkit, exposes a specialized control as a native window. By using QWindow::fromWinId() to wrap the native window handle in a QWindow, the window can then be re-parented into the Qt window hierarchy as any other QWindow. To re-parent this QWindow into a Qt Widget based UI, use the widgets-specific QWidget::createWindowContainer() function.

Event Handling

Most event handling use-cases in Qt are sufficiently covered by the cross platform event delivery, via QWindow::event() and friends, or through QObject::installEventFilter().

In cases where this is not enough, Qt provides access to the delivery of the native events. A global event filter that receives all native events can be installed by using QCoreApplication::installNativeEventFilter(), while per-window native events can be handled in QWindow::nativeEvent().

Note: Interfering with the native event flow may put Qt in an inconsistent state. These APIs should primarily be used to augment Qt's existing event handling, for example for events Qt doesn't handle yet.

Native Interfaces

Platform specific functionality not covered by the APIs mentioned above are handled by the new generic native interface mechanism. This mechanism replaces the platform headers user-facing API, as well as the QPA-level QPlatformNativeInterface API. The interfaces provide access to native or platform specific APIs of the classes they extend.

The interfaces live in the QNativeInterface namespace, and cover use-cases such as accessing underlying native handles, adopting existing native handles, or providing platform specific APIs.

The majority of the old platform header APIs can be found in the QNativeInterface::Private namespace, since these were largely used by other internal code. Over time we'll expose more of these APIs based on feedback and use-cases.

Accessing underlying native handles

In situations where a feature of the native platform is not exposed in Qt, it can be helpful to access the native handles maintained by Qt, and use those to call the native APIs instead.

For example, to access the underlying NSOpenGLContext of an QOpenGLContext on macOS, via the QNativeInterface::QCocoaGLContext native interface:

using namespace QNativeInterface;if (auto *cocoaGLContext = glContext->nativeInterface<QCocoaGLContext>())    [cocoaGLContext->nativeContext() makeCurrentContext];

The native interface is accessed through the QOpenGLContext::nativeInterface() accessor, which ensures that the requested interface is available, and otherwise returns nullptr. The underlying NSOpenGLContext is then accessed through the nativeContext() accessor.

Adopting existing native handles

Similarly to the window embedding use-case, there are situations where the native platform, or another toolkit, has created a native handle that you would like to pass on to Qt — wrapping the existing handle instead of creating a new one.

For example, to adopt an existing NSOpenGLContext, and use that to share resources with a context created by Qt:

using namespace QNativeInterface;QOpenGLContext *adoptedContext = QCocoaGLContext::fromNativeContext(nsOpenGLContext);anotherContext->setShareContext(adoptedContext);

The adopted context is created by a platform specific factory function in the QNativeInterface::QCocoaGLContext native interface.

Accessing platform specific APIs

In some cases an API is too platform specific to be included in the cross platform Qt classes, but is still useful to include. These APIs are available either in the same way as when accessing the underlying native handles, through the nativeInterface() accessor, or directly as static function in the native interface.

For example, to obtain the OpenGL module handle on Windows:

using namespace QNativeInterface;HMODULE moduleHandle = QWGLContext::openGLModuleHandle();

Or to tweak the border behavior of a window on Windows, via its platform window handle:

using namespace QNativeInterface::Private;if (auto *windowsWindow = dynamic_cast<QWindowsWindow*>(window->handle()))    windowsWindow->setHasBorderInFullScreen(true);

Source and Binary Compatibility

One important thing to note is that are no source or binary compatibility guarantees for the native interface APIs, meaning that an application using these interfaces is only guaranteed to work with the Qt version it was developed against. This allows us to adjust and add to these APIs as needed -- making them more flexible in tracking the underlying native functionality.

Extras modules

As some of you have noticed, the "extras" modules are not part of the initial Qt 6.0 release. This is related to the work described in this blog post, as we still need to go through these modules to survey:

  • Whether any features are deprecated and can be removed
  • Whether any features have more modern replacements that we should advocate instead
  • Whether any features can be better solved by integrating directly with the native APIs
  • Whether any features fit better in the API paradigms described earlier, for example as native interfaces

The end goal would ideally be that we don't need any standalone "extras" module, but rather that the functionality is available directly in the relevant modules, e.g. QtGui or QtDeclarative.  If you want to track this work you can follow QTBUG-83251.


Blog Topics:

Comments

Commenting for this post has ended.

P
Pau
5 points
55 months ago

How come Qt 6 still uses moc instead of taking verdigris or implementing its own template-based moc replacement? https://github.com/woboq/verdigris

Mykola Krachkovsky
1 point
55 months ago
  1. That "replacement" doesn't support some features, has own syntax and needs code duplicate, so it's not an actual replacement.
  2. Why? moc is just a tool to make life easier.
P
Pau
0 points
55 months ago
  1. Actually, it's the other way around: moc does not support templated QObjects, headers with raw string literals and plenty of other modern C++ features. Its own syntax is needed just to avoid clashing with moc, that need is gone once moc is gone. As for code duplicate, please elaborate. Verdigris has been an actual full moc replacement for years.
  2. Because moc is unneeded, as verdigris proves, and it has some limitations that verdigris lifts. Also, two preprocessors (cpp and moc) make errors sometimes difficult to debug.
Mykola Krachkovsky
2 points
55 months ago

1.1. Status in readme has several features it misses.

1.2. Using insted of public slots: void mySlot(int x);void mySlot(int x); W_SLOT(mySlot)is code duplication, you have to write (and rename in case of refactoring) same slot twice. And this is incompatible with existing code. So this is not a replacement.

2. That code can't replace existing moc, so no, moc is still needed.

K
Kelteseth
3 points
55 months ago

I have written a blog post about cmake for qmake people: https://screen-play.app/blog/qmake_to_cmake/

David Grayson
2 points
55 months ago

Could you please document the right way to compile the "host Qt" specified by -qt-host-path so we only build the needed tools like moc and uic, instead of compiling all of Qt? For anyone like me who only wants to cross-compile Qt and not have a host version, this will save us a lot of time.

nn jj
1 point
55 months ago

qmake was an excellent build system. but because of lacking of document and lots of undocumented hacks. I gradually switch to cmake..

Violet Giraffe
1 point
55 months ago

Cmake is one of the worst build systems I've ever seen as soon as you need to do anything beyond simply listing your headers, SRC and INCLUDEPATH. I hope qmake will continue on, perhaps someone will maintain its fork. Contrary to CMake, qmake is the easiest to use build system I know and it easily supports complicated configuration and a bit of scripting. I use it even for non-qt project for its simplicity and flexibility.

Richard Weickelt
3 points
55 months ago

Yes, the CMake language is pure horror, but I'd claim that adding own rules is not more difficult than with qmake.

Try Qbs. Its even more flexible and uses a much more powerful language than qmake: https://www.qt.io/blog/qbs-1-17-released

E
Energo Koder
0 points
55 months ago

I have created generic CMake scripts which allow to create C++ (with or without Qt) apps and libs very easily: https://gitlab.com/energokoder/energo-budowa

Daniel Nicoletti
0 points
55 months ago

Last time I tried CMake with Qt on iOS I couldn't get far, is it supported on iOS already? Are there some pages with tips?

J
Jörg Bornemann
1 point
55 months ago

Yes, it's supported. Maybe the "Cross compiling for iOS" section in qtbase/cmake/README.md is helpful.

Mykola Krachkovsky
0 points
55 months ago

Are this instructions still valid? I've downloaded full archive (http://download.qt.io/development_releases/qt/6.0/6.0.0-alpha/single/qt-everywhere-src-6.0.0-alpha.tar.xz) and after

export LLVM_INSTALL_DIR=/usr/lib64/cmake/clang

./configure -cmake -prefix ~/opt/qt6a

cmake --build . --parallel

got: ninja: error: loading 'build.ninja': No such file or directory

Also, test vulkan-server-buffer fails, because

#define VK_USE_PLATFORM_WAYLAND_KHR 1

#include <vulkan/vulkan.h>

also needs wayland-client.h but there is no dependency for wayland-client.

Mykola Krachkovsky
0 points
55 months ago

Solved problem by direct cmake call — everything is fine with build.ninja then (also made other build directory):

cmake -GNinja -DCMAKE_INSTALL_PREFIX=<prefix> ../qt-everywhere-src-6.0.0-alpha

offtopic: This commento plugin is buggy — editing ruins formatting — and cant reply to self.

J
Jörg Bornemann
-1 points
54 months ago

The instructions here are still valid. If you encounter a bug, please report it via the bug tracker.

Off-topic: Sorry for getting this late back to you. Apparently I'm not getting email notifications for comments. There seems to be something off with this commenting facility.

Andrei Cherniaev
0 points
52 months ago

It is the most important in Qt 6! Great job.

The are a lot of about cross-compiling. But as I know the most interesting in 2021 is compiling Qt on ARM64 directly. May be you add some unit-test of building system for OrangePi boards and etc?