Qt Contributor Summit 2019 - Platform-specific APIs in Qt 6
There are many different ways to expose platform-specific functionality in Qt. The session was about what we could do to improve on this situation in Qt 6, now that we have the opportunity to reshuffle some things and even break some of the low-level APIs.
The current situation and problem is summarized here: QTBUG-80233
- QPlatformNativeInterface which is low-level, private and "flexible".
- Qt Platform Headers which is a public interface into this.
- QFooExtras modules for exposing functionality that didn't fit elsewhere and which was useful in certain cases (wrappers, type conversions, etc.). These have a tendency to become stale and they also discourage cross-platform solutions because it is really easy to add a platform-specific implementation there and postpone the generalization of it for later.
- Qt Wayland Client (a sort of extras module but with a different name)
- Functions around Qt: QOpenGLContext has a "nativeHandle" which returns a QVariant
- QMacNativeWidget and others in Qt Widgets which do basically the same as fromWinId()
- Qt Purchasing has setPlatformProperty() which takes two strings, and Qt Location is inventing similar things
It seems that it is not only a QPA specific problem, but a general problem for exposing APIs that are specific to a single plugin (QPA, Qt Multimedia, Qt Location, etc.)
The goal would be to generalize, try to adopt common patterns in Qt as much as possible.
Move functions where they belong
If we need a function which converts e.g. a QImage to a native image resource, then instead of separating this out, it should go into QImage behind #ifdefs. If it returns a SUPEROS_IMAGE_PTR type anyway, then you cannot use it in any meaningful way without including system headers, so there is no real risk of confusing users into writing platform-specific code that they think is cross-platform. Instead, QImage gets a toSuperOSImagePtr() function.
This has already be done for many things (see QString::toCFString() which was moved in Qt 5.2). We should finish the job and move everything that makes sense.
A suggested approach to get rid of this was to give the QPA plugins explicit APIs through libraries. So each PlatformFooPlugin is just a thin wrapper around the functionality which resides inside a PlatformFoo library and which can expose an API as a regular module. This would then make it possible to access functionality inside the plugin with proper types and static validation. It would also expand to other plugin-systems, such as Qt Multimedia, Qt Location, etc.
One open question is how that API should look: If it is virtual functions, does that mean we cannot add APIs later and will end up with a bunch of V2 and V3 classes? Or could the compatibility guarantees be the same as for Qt Platform Headers for instance, where binary compatibility is not guaranteed but source is.
Qt Wayland Client also has its own plugins again, which implement Wayland extensions: Should each of these plugins come with a separate module and API? We would end up with a lot of very small modules. There was no decision made, but it might make sense to keep APIs for things that are common to multiple different extensions in the main Wayland Client module, but maybe make specific modules for very specialized stuff.
However, on Linux, the fact is that the application programmer might not know which platform plugin they are going to run on, and they may still want to access platform-specific APIs that they know are common to most. They may even want to run on platform plugins which are part of the target Linux distro and not even invented at the time of writing the plugin. The way this is done today is by having an unwritten convention that e.g. "eglDisplay" is the key to use if you want the EGLDisplay, and all plugins writers have to know this.
It would be possible to solve the problem by having interfaces for these types of things (it would require actually defining and documenting the conventions, but maybe that would useful anyway?) For instance, you could have a QEGLScreenInterface which exposes a function that returns the EglDisplay. Then you could use Q_INTERFACES in the implementation, and qobject_cast when accessing the code to cast to the proper interface, like we currently do with plugins.
But it seemed like the consensus was that this would not be worth it for all cases where QPlatformNativeInterface is used and that we should keep it, but try to remove stuff from it as much as possible.
One suggestion was that if we do keep that access level, then we could at least move all the functions into their respective QPlatformFoo classes (instead of having a QPlatformNativeInterface::resourceForScreen() you would have QPlatformScreen::resource(). The concern was raised that maybe we are adding a lot of work which is not necessarily useful to our users, since this is low-level and the two approaches are essentially the same. However, doing it might not take very long either, since it is just about moving stuff around in the QPA plugins and the interfaces. It would of course also impose work on maintainers of other QPA plugins which do not reside in Qt, so we just need to make a decision about the trade-off. If we ever want to do a clean-up like this, then Qt 6 is certainly the right time.
Qt Platform Headers
This can be removed if we make sure there is a module and API for each plugin in itself. We can probably not deprecate it in Qt 5, since we need the alternative to be in place first, but we should mark it as obsolete in the docs to make sure it is not used by new code.
winId() and fromWinId()
Remove stuff like QMacNativeWidget and focus on using winId() and fromWinId() on all platforms in a consistent way.
We need to start by seeing if the things in there can be moved into other places or if there are leftovers that truly do not fit anywhere else. If there are, maybe we keep the modules but try to formalize how they are structured? Or maybe the modules can be removed entirely and the APIs moved to other modules, such as the QPA plugin module.