diff options
Diffstat (limited to 'src/gui/platform/unix')
-rw-r--r-- | src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp | 10 | ||||
-rw-r--r-- | src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h | 4 | ||||
-rw-r--r-- | src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp | 14 | ||||
-rw-r--r-- | src/gui/platform/unix/dbustray/qdbustrayicon.cpp | 12 | ||||
-rw-r--r-- | src/gui/platform/unix/dbustray/qdbustraytypes.cpp | 6 | ||||
-rw-r--r-- | src/gui/platform/unix/qgenericunixservices.cpp | 224 | ||||
-rw-r--r-- | src/gui/platform/unix/qgenericunixservices_p.h | 1 | ||||
-rw-r--r-- | src/gui/platform/unix/qgenericunixthemes.cpp | 668 | ||||
-rw-r--r-- | src/gui/platform/unix/qgenericunixthemes_p.h | 4 | ||||
-rw-r--r-- | src/gui/platform/unix/qunixnativeinterface.cpp | 93 | ||||
-rw-r--r-- | src/gui/platform/unix/qxkbcommon.cpp | 57 | ||||
-rw-r--r-- | src/gui/platform/unix/qxkbcommon_p.h | 57 |
12 files changed, 916 insertions, 234 deletions
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp index 9391b77f6a..1023b16662 100644 --- a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp @@ -40,14 +40,14 @@ QDBusMenuConnection::QDBusMenuConnection(QObject *parent, const QString &service , m_connection(serviceName.isNull() ? QDBusConnection::sessionBus() : QDBusConnection::connectToBus(QDBusConnection::SessionBus, serviceName)) , m_dbusWatcher(new QDBusServiceWatcher(StatusNotifierWatcherService, m_connection, QDBusServiceWatcher::WatchForRegistration, this)) - , m_statusNotifierHostRegistered(false) + , m_watcherRegistered(false) { #ifndef QT_NO_SYSTEMTRAYICON - QDBusInterface systrayHost(StatusNotifierWatcherService, StatusNotifierWatcherPath, StatusNotifierWatcherService, m_connection); - if (systrayHost.isValid() && systrayHost.property("IsStatusNotifierHostRegistered").toBool()) - m_statusNotifierHostRegistered = true; + // Start monitoring if any known tray-related services are registered. + if (m_connection.interface()->isServiceRegistered(StatusNotifierWatcherService)) + m_watcherRegistered = true; else - qCDebug(qLcMenu) << "StatusNotifierHost is not registered"; + qCDebug(qLcMenu) << "failed to find service" << StatusNotifierWatcherService; #endif } diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h index 69713b12bd..37033e2fa3 100644 --- a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h @@ -39,7 +39,7 @@ public: ~QDBusMenuConnection(); QDBusConnection connection() const { return m_connection; } QDBusServiceWatcher *dbusWatcher() const { return m_dbusWatcher; } - bool isStatusNotifierHostRegistered() const { return m_statusNotifierHostRegistered; } + bool isWatcherRegistered() const { return m_watcherRegistered; } #ifndef QT_NO_SYSTEMTRAYICON bool registerTrayIconMenu(QDBusTrayIcon *item); void unregisterTrayIconMenu(QDBusTrayIcon *item); @@ -60,7 +60,7 @@ private: QString m_serviceName; QDBusConnection m_connection; QDBusServiceWatcher *m_dbusWatcher; - bool m_statusNotifierHostRegistered; + bool m_watcherRegistered; }; QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp index d2469a0d26..b7fd035883 100644 --- a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp +++ b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp @@ -217,19 +217,19 @@ QDBusMenuShortcut QDBusMenuItem::convertKeySequence(const QKeySequence &sequence QDBusMenuShortcut shortcut; for (int i = 0; i < sequence.count(); ++i) { QStringList tokens; - int key = sequence[i].toCombined(); - if (key & Qt::MetaModifier) + auto modifiers = sequence[i].keyboardModifiers(); + if (modifiers & Qt::MetaModifier) tokens << QStringLiteral("Super"); - if (key & Qt::ControlModifier) + if (modifiers & Qt::ControlModifier) tokens << QStringLiteral("Control"); - if (key & Qt::AltModifier) + if (modifiers & Qt::AltModifier) tokens << QStringLiteral("Alt"); - if (key & Qt::ShiftModifier) + if (modifiers & Qt::ShiftModifier) tokens << QStringLiteral("Shift"); - if (key & Qt::KeypadModifier) + if (modifiers & Qt::KeypadModifier) tokens << QStringLiteral("Num"); - QString keyName = QKeySequencePrivate::keyName(key, QKeySequence::PortableText); + QString keyName = QKeySequencePrivate::keyName(sequence[i].key(), QKeySequence::PortableText); if (keyName == "+"_L1) tokens << QStringLiteral("plus"); else if (keyName == "-"_L1) diff --git a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp index 18334e5715..0dff9b598e 100644 --- a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp +++ b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp @@ -198,7 +198,10 @@ QTemporaryFile *QDBusTrayIcon::tempIcon(const QIcon &icon) if (!necessary) return nullptr; QTemporaryFile *ret = new QTemporaryFile(tempFileTemplate(), this); - ret->open(); + if (!ret->open()) { + delete ret; + return nullptr; + } icon.pixmap(QSize(22, 22)).save(ret); ret->close(); return ret; @@ -331,8 +334,11 @@ void QDBusTrayIcon::notificationClosed(uint id, uint reason) bool QDBusTrayIcon::isSystemTrayAvailable() const { QDBusMenuConnection * conn = const_cast<QDBusTrayIcon *>(this)->dBusConnection(); - qCDebug(qLcTray) << conn->isStatusNotifierHostRegistered(); - return conn->isStatusNotifierHostRegistered(); + + // If the KDE watcher service is registered, we must be on a desktop + // where a StatusNotifier-conforming system tray exists. + qCDebug(qLcTray) << conn->isWatcherRegistered(); + return conn->isWatcherRegistered(); } QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbustray/qdbustraytypes.cpp b/src/gui/platform/unix/dbustray/qdbustraytypes.cpp index b2d87d7b8c..accbd87e7e 100644 --- a/src/gui/platform/unix/dbustray/qdbustraytypes.cpp +++ b/src/gui/platform/unix/dbustray/qdbustraytypes.cpp @@ -45,7 +45,7 @@ QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon) bool hasSmallIcon = false; bool hasMediumIcon = false; QList<QSize> toRemove; - for (const QSize &size : qAsConst(sizes)) { + for (const QSize &size : std::as_const(sizes)) { int maxSize = qMax(size.width(), size.height()); if (maxSize <= IconNormalSmallSize) hasSmallIcon = true; @@ -54,7 +54,7 @@ QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon) else if (maxSize > IconSizeLimit) toRemove << size; } - for (const QSize &size : qAsConst(toRemove)) + for (const QSize &size : std::as_const(toRemove)) sizes.removeOne(size); if (!hasSmallIcon) sizes.append(QSize(IconNormalSmallSize, IconNormalSmallSize)); @@ -62,7 +62,7 @@ QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon) sizes.append(QSize(IconNormalMediumSize, IconNormalMediumSize)); ret.reserve(sizes.size()); - for (const QSize &size : qAsConst(sizes)) { + for (const QSize &size : std::as_const(sizes)) { // Protocol specifies ARGB32 format in network byte order QImage im = engine->pixmap(size, QIcon::Normal, QIcon::Off).toImage().convertToFormat(QImage::Format_ARGB32); // letterbox if necessary to make it square diff --git a/src/gui/platform/unix/qgenericunixservices.cpp b/src/gui/platform/unix/qgenericunixservices.cpp index eb66e5b1a4..bfd2556b1e 100644 --- a/src/gui/platform/unix/qgenericunixservices.cpp +++ b/src/gui/platform/unix/qgenericunixservices.cpp @@ -5,6 +5,9 @@ #include <QtGui/private/qtguiglobal_p.h> #include "qguiapplication.h" #include "qwindow.h" +#include <QtGui/qpa/qplatformwindow_p.h> +#include <QtGui/qpa/qplatformwindow.h> +#include <QtGui/qpa/qplatformnativeinterface.h> #include <QtCore/QDebug> #include <QtCore/QFile> @@ -128,8 +131,13 @@ static inline bool detectWebBrowser(const QByteArray &desktop, return false; } -static inline bool launch(const QString &launcher, const QUrl &url) +static inline bool launch(const QString &launcher, const QUrl &url, + const QString &xdgActivationToken) { + if (!xdgActivationToken.isEmpty()) { + qputenv("XDG_ACTIVATION_TOKEN", xdgActivationToken.toUtf8()); + } + const QString command = launcher + u' ' + QLatin1StringView(url.toEncoded()); if (debug) qDebug("Launching %s", qPrintable(command)); @@ -145,16 +153,20 @@ static inline bool launch(const QString &launcher, const QUrl &url) #endif if (!ok) qWarning("Launch failed (%s)", qPrintable(command)); + + qunsetenv("XDG_ACTIVATION_TOKEN"); + return ok; } #if QT_CONFIG(dbus) static inline bool checkNeedPortalSupport() { - return !QStandardPaths::locate(QStandardPaths::RuntimeLocation, "flatpak-info"_L1).isEmpty() || qEnvironmentVariableIsSet("SNAP"); + return QFileInfo::exists("/.flatpak-info"_L1) || qEnvironmentVariableIsSet("SNAP"); } -static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url, const QString &parentWindow) +static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url, const QString &parentWindow, + const QString &xdgActivationToken) { // DBus signature: // OpenFile (IN s parent_window, @@ -165,8 +177,7 @@ static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url, const QStri // handle_token (s) - A string that will be used as the last element of the @handle. // writable (b) - Whether to allow the chosen application to write to the file. -#ifdef O_PATH - const int fd = qt_safe_open(QFile::encodeName(url.toLocalFile()), O_PATH); + const int fd = qt_safe_open(QFile::encodeName(url.toLocalFile()), O_RDONLY); if (fd != -1) { QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, @@ -176,21 +187,22 @@ static inline QDBusMessage xdgDesktopPortalOpenFile(const QUrl &url, const QStri QDBusUnixFileDescriptor descriptor; descriptor.giveFileDescriptor(fd); - const QVariantMap options = {{"writable"_L1, true}}; + QVariantMap options = {}; + + if (!xdgActivationToken.isEmpty()) { + options.insert("activation_token"_L1, xdgActivationToken); + } message << parentWindow << QVariant::fromValue(descriptor) << options; return QDBusConnection::sessionBus().call(message); } -#else - Q_UNUSED(url); - Q_UNUSED(parentWindow) -#endif return QDBusMessage::createError(QDBusError::InternalError, qt_error_string()); } -static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url, const QString &parentWindow) +static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url, const QString &parentWindow, + const QString &xdgActivationToken) { // DBus signature: // OpenURI (IN s parent_window, @@ -208,12 +220,19 @@ static inline QDBusMessage xdgDesktopPortalOpenUrl(const QUrl &url, const QStrin "org.freedesktop.portal.OpenURI"_L1, "OpenURI"_L1); // FIXME parent_window_id and handle writable option - message << parentWindow << url.toString() << QVariantMap(); + QVariantMap options; + + if (!xdgActivationToken.isEmpty()) { + options.insert("activation_token"_L1, xdgActivationToken); + } + + message << parentWindow << url.toString() << options; return QDBusConnection::sessionBus().call(message); } -static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url, const QString &parentWindow) +static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url, const QString &parentWindow, + const QString &xdgActivationToken) { // DBus signature: // ComposeEmail (IN s parent_window, @@ -248,6 +267,10 @@ static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url, const QStr options.insert("attachment_fds"_L1, QVariant::fromValue(attachments)); #endif + if (!xdgActivationToken.isEmpty()) { + options.insert("activation_token"_L1, xdgActivationToken); + } + QDBusMessage message = QDBusMessage::createMethodCall("org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, "org.freedesktop.portal.Email"_L1, @@ -331,9 +354,13 @@ private Q_SLOTS: { if (result != 0) return; - XDGDesktopColor color{}; - map.value(u"color"_s).value<QDBusArgument>() >> color; - Q_EMIT colorPicked(color.toQColor()); + if (map.contains(u"color"_s)) { + XDGDesktopColor color{}; + map.value(u"color"_s).value<QDBusArgument>() >> color; + Q_EMIT colorPicked(color.toQColor()); + } else { + Q_EMIT colorPicked({}); + } deleteLater(); } @@ -347,6 +374,9 @@ private: QGenericUnixServices::QGenericUnixServices() { #if QT_CONFIG(dbus) + if (qEnvironmentVariableIntValue("QT_NO_XDG_DESKTOP_PORTAL") > 0) { + return; + } QDBusMessage message = QDBusMessage::createMethodCall( "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1, "org.freedesktop.DBus.Properties"_L1, "Get"_L1); @@ -389,60 +419,117 @@ QByteArray QGenericUnixServices::desktopEnvironment() const return result; } +template<typename F> +void runWithXdgActivationToken(F &&functionToCall) +{ +#if QT_CONFIG(wayland) + QWindow *window = qGuiApp->focusWindow(); + + if (!window) { + functionToCall({}); + return; + } + + auto waylandApp = dynamic_cast<QNativeInterface::QWaylandApplication *>( + qGuiApp->platformNativeInterface()); + auto waylandWindow = + dynamic_cast<QNativeInterface::Private::QWaylandWindow *>(window->handle()); + + if (!waylandWindow || !waylandApp) { + functionToCall({}); + return; + } + + QObject::connect(waylandWindow, + &QNativeInterface::Private::QWaylandWindow::xdgActivationTokenCreated, + waylandWindow, functionToCall, Qt::SingleShotConnection); + waylandWindow->requestXdgActivationToken(waylandApp->lastInputSerial()); +#else + functionToCall({}); +#endif +} + bool QGenericUnixServices::openUrl(const QUrl &url) { - if (url.scheme() == "mailto"_L1) { -#if QT_CONFIG(dbus) + auto openUrlInternal = [this](const QUrl &url, const QString &xdgActivationToken) { + if (url.scheme() == "mailto"_L1) { +# if QT_CONFIG(dbus) + if (checkNeedPortalSupport()) { + const QString parentWindow = QGuiApplication::focusWindow() + ? portalWindowIdentifier(QGuiApplication::focusWindow()) + : QString(); + QDBusError error = xdgDesktopPortalSendEmail(url, parentWindow, xdgActivationToken); + if (!error.isValid()) + return true; + + // service not running, fall back + } +# endif + return openDocument(url); + } + +# if QT_CONFIG(dbus) if (checkNeedPortalSupport()) { const QString parentWindow = QGuiApplication::focusWindow() ? portalWindowIdentifier(QGuiApplication::focusWindow()) : QString(); - QDBusError error = xdgDesktopPortalSendEmail(url, parentWindow); + QDBusError error = xdgDesktopPortalOpenUrl(url, parentWindow, xdgActivationToken); if (!error.isValid()) return true; + } +# endif - // service not running, fall back + if (m_webBrowser.isEmpty() + && !detectWebBrowser(desktopEnvironment(), true, &m_webBrowser)) { + qWarning("Unable to detect a web browser to launch '%s'", qPrintable(url.toString())); + return false; } -#endif - return openDocument(url); - } + return launch(m_webBrowser, url, xdgActivationToken); + }; -#if QT_CONFIG(dbus) - if (checkNeedPortalSupport()) { - const QString parentWindow = QGuiApplication::focusWindow() - ? portalWindowIdentifier(QGuiApplication::focusWindow()) - : QString(); - QDBusError error = xdgDesktopPortalOpenUrl(url, parentWindow); - if (!error.isValid()) - return true; - } -#endif + if (QGuiApplication::platformName().startsWith("wayland"_L1)) { + runWithXdgActivationToken( + [openUrlInternal, url](const QString &token) { openUrlInternal(url, token); }); + + return true; - if (m_webBrowser.isEmpty() && !detectWebBrowser(desktopEnvironment(), true, &m_webBrowser)) { - qWarning("Unable to detect a web browser to launch '%s'", qPrintable(url.toString())); - return false; + } else { + return openUrlInternal(url, QString()); } - return launch(m_webBrowser, url); } bool QGenericUnixServices::openDocument(const QUrl &url) { -#if QT_CONFIG(dbus) - if (checkNeedPortalSupport()) { - const QString parentWindow = QGuiApplication::focusWindow() - ? portalWindowIdentifier(QGuiApplication::focusWindow()) - : QString(); - QDBusError error = xdgDesktopPortalOpenFile(url, parentWindow); - if (!error.isValid()) - return true; - } -#endif + auto openDocumentInternal = [this](const QUrl &url, const QString &xdgActivationToken) { + +# if QT_CONFIG(dbus) + if (checkNeedPortalSupport()) { + const QString parentWindow = QGuiApplication::focusWindow() + ? portalWindowIdentifier(QGuiApplication::focusWindow()) + : QString(); + QDBusError error = xdgDesktopPortalOpenFile(url, parentWindow, xdgActivationToken); + if (!error.isValid()) + return true; + } +# endif + + if (m_documentLauncher.isEmpty() + && !detectWebBrowser(desktopEnvironment(), false, &m_documentLauncher)) { + qWarning("Unable to detect a launcher for '%s'", qPrintable(url.toString())); + return false; + } + return launch(m_documentLauncher, url, xdgActivationToken); + }; + + if (QGuiApplication::platformName().startsWith("wayland"_L1)) { + runWithXdgActivationToken([openDocumentInternal, url](const QString &token) { + openDocumentInternal(url, token); + }); - if (m_documentLauncher.isEmpty() && !detectWebBrowser(desktopEnvironment(), false, &m_documentLauncher)) { - qWarning("Unable to detect a launcher for '%s'", qPrintable(url.toString())); - return false; + return true; + } else { + return openDocumentInternal(url, QString()); } - return launch(m_documentLauncher, url); } #else @@ -477,9 +564,7 @@ QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent) QString QGenericUnixServices::portalWindowIdentifier(QWindow *window) { - if (QGuiApplication::platformName() == QLatin1String("xcb")) - return "x11:"_L1 + QString::number(window->winId(), 16); - + Q_UNUSED(window); return QString(); } @@ -492,6 +577,37 @@ bool QGenericUnixServices::hasCapability(Capability capability) const return false; } +void QGenericUnixServices::setApplicationBadge(qint64 number) +{ +#if QT_CONFIG(dbus) + if (qGuiApp->desktopFileName().isEmpty()) { + qWarning("QGuiApplication::desktopFileName() is empty"); + return; + } + + + const QString launcherUrl = QStringLiteral("application://") + qGuiApp->desktopFileName() + QStringLiteral(".desktop"); + const qint64 count = qBound(0, number, 9999); + QVariantMap dbusUnityProperties; + + if (count > 0) { + dbusUnityProperties[QStringLiteral("count")] = count; + dbusUnityProperties[QStringLiteral("count-visible")] = true; + } else { + dbusUnityProperties[QStringLiteral("count-visible")] = false; + } + + auto signal = QDBusMessage::createSignal(QStringLiteral("/com/canonical/unity/launcherentry/") + + qGuiApp->applicationName(), QStringLiteral("com.canonical.Unity.LauncherEntry"), QStringLiteral("Update")); + + signal.setArguments({launcherUrl, dbusUnityProperties}); + + QDBusConnection::sessionBus().send(signal); +#else + Q_UNUSED(number) +#endif +} + QT_END_NAMESPACE #include "qgenericunixservices.moc" diff --git a/src/gui/platform/unix/qgenericunixservices_p.h b/src/gui/platform/unix/qgenericunixservices_p.h index 701bcfc78f..56e15103f7 100644 --- a/src/gui/platform/unix/qgenericunixservices_p.h +++ b/src/gui/platform/unix/qgenericunixservices_p.h @@ -35,6 +35,7 @@ public: bool openDocument(const QUrl &url) override; QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr) override; + void setApplicationBadge(qint64 number); virtual QString portalWindowIdentifier(QWindow *window); private: diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp index 4901433b15..8a7f7cd6f7 100644 --- a/src/gui/platform/unix/qgenericunixthemes.cpp +++ b/src/gui/platform/unix/qgenericunixthemes.cpp @@ -33,6 +33,12 @@ #include <QDBusConnectionInterface> #include <private/qdbusplatformmenu_p.h> #include <private/qdbusmenubar_p.h> +#include <private/qflatmap_p.h> +#include <QJsonDocument> +#include <QJsonArray> +#include <QJsonObject> +#include <QJsonValue> +#include <QJsonParseError> #endif #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) #include <private/qdbustrayicon_p.h> @@ -72,20 +78,31 @@ static const char defaultFixedFontNameC[] = "monospace"; enum { defaultSystemFontSize = 9 }; #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) -static bool isDBusTrayAvailable() { - static bool dbusTrayAvailable = false; - static bool dbusTrayAvailableKnown = false; - if (!dbusTrayAvailableKnown) { - QDBusMenuConnection conn; - if (conn.isStatusNotifierHostRegistered()) - dbusTrayAvailable = true; - dbusTrayAvailableKnown = true; - qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable; - } - return dbusTrayAvailable; +static bool shouldUseDBusTray() { + // There's no other tray implementation to fallback to on non-X11 + // and QDBusTrayIcon can register the icon on the fly after creation + if (QGuiApplication::platformName() != "xcb"_L1) + return true; + const bool result = QDBusMenuConnection().isWatcherRegistered(); + qCDebug(qLcTray) << "D-Bus tray available:" << result; + return result; } #endif +static QString mouseCursorTheme() +{ + static QString themeName = qEnvironmentVariable("XCURSOR_THEME"); + return themeName; +} + +static QSize mouseCursorSize() +{ + constexpr int defaultCursorSize = 24; + static const int xCursorSize = qEnvironmentVariableIntValue("XCURSOR_SIZE"); + static const int s = xCursorSize > 0 ? xCursorSize : defaultCursorSize; + return QSize(s, s); +} + #ifndef QT_NO_DBUS static bool checkDBusGlobalMenuAvailable() { @@ -105,7 +122,7 @@ static bool isDBusGlobalMenuAvailable() /*! * \internal * The QGenericUnixThemeDBusListener class listens to the SettingChanged DBus signal - * and translates it into the QDbusSettingType enum. + * and translates it into combinations of the enums \c Provider and \c Setting. * Upon construction, it logs success/failure of the DBus connection. * * The signal settingChanged delivers the normalized setting type and the new value as a string. @@ -117,35 +134,98 @@ class QGenericUnixThemeDBusListener : public QObject Q_OBJECT public: - QGenericUnixThemeDBusListener(const QString &service, const QString &path, const QString &interface, const QString &signal); - enum class SettingType { - KdeGlobalTheme, - KdeApplicationStyle, - GtkTheme, - Unknown + enum class Provider { + Kde, + Gtk, + Gnome, + }; + Q_ENUM(Provider) + + enum class Setting { + Theme, + ApplicationStyle, + ColorScheme, }; - Q_ENUM(SettingType) + Q_ENUM(Setting) - static SettingType toSettingType(const QString &location, const QString &key); + QGenericUnixThemeDBusListener(); + QGenericUnixThemeDBusListener(const QString &service, const QString &path, + const QString &interface, const QString &signal); private Q_SLOTS: void onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value); Q_SIGNALS: - void settingChanged(QGenericUnixThemeDBusListener::SettingType type, const QString &value); + void settingChanged(QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value); +private: + struct DBusKey + { + QString location; + QString key; + DBusKey(const QString &loc, const QString &k) : location(loc), key(k) {}; + bool operator<(const DBusKey &other) const + { + return location + key < other.location + other.key; + } + }; + + struct ChangeSignal + { + Provider provider; + Setting setting; + ChangeSignal(Provider p, Setting s) : provider(p), setting(s) {} + ChangeSignal() {} + }; + + // Json keys + static constexpr QLatin1StringView s_dbusLocation = QLatin1StringView("DBusLocation"); + static constexpr QLatin1StringView s_dbusKey = QLatin1StringView("DBusKey"); + static constexpr QLatin1StringView s_provider = QLatin1StringView("Provider"); + static constexpr QLatin1StringView s_setting = QLatin1StringView("Setting"); + static constexpr QLatin1StringView s_signals = QLatin1StringView("DbusSignals"); + static constexpr QLatin1StringView s_root = QLatin1StringView("Qt.qpa.DBusSignals"); + + QFlatMap <DBusKey, ChangeSignal> m_signalMap; + + void init(const QString &service, const QString &path, + const QString &interface, const QString &signal); + + std::optional<ChangeSignal> findSignal(const QString &location, const QString &key) const; + void populateSignalMap(); + void loadJson(const QString &fileName); + void saveJson(const QString &fileName) const; }; QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(const QString &service, const QString &path, const QString &interface, const QString &signal) { + init (service, path, interface, signal); +} + +QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener() +{ + static constexpr QLatin1StringView service(""); + static constexpr QLatin1StringView path("/org/freedesktop/portal/desktop"); + static constexpr QLatin1StringView interface("org.freedesktop.portal.Settings"); + static constexpr QLatin1StringView signal("SettingChanged"); + + init (service, path, interface, signal); +} + +void QGenericUnixThemeDBusListener::init(const QString &service, const QString &path, + const QString &interface, const QString &signal) +{ QDBusConnection dbus = QDBusConnection::sessionBus(); const bool dBusRunning = dbus.isConnected(); bool dBusSignalConnected = false; #define LOG service << path << interface << signal; if (dBusRunning) { + populateSignalMap(); qRegisterMetaType<QDBusVariant>(); dBusSignalConnected = dbus.connect(service, path, interface, signal, this, SLOT(onSettingChanged(QString,QString,QDBusVariant))); @@ -168,26 +248,155 @@ QGenericUnixThemeDBusListener::QGenericUnixThemeDBusListener(const QString &serv #undef LOG } -QGenericUnixThemeDBusListener::SettingType QGenericUnixThemeDBusListener::toSettingType( - const QString &location, const QString &key) -{ - if (location == QLatin1StringView("org.kde.kdeglobals.KDE") - && key == QLatin1StringView("widgetStyle")) - return SettingType::KdeApplicationStyle; - if (location == QLatin1StringView("org.kde.kdeglobals.General") - && key == QLatin1StringView("ColorScheme")) - return SettingType::KdeGlobalTheme; - if (location == QLatin1StringView("org.gnome.desktop.interface") - && key == QLatin1StringView("gtk-theme")) - return SettingType::GtkTheme; - return SettingType::Unknown; +void QGenericUnixThemeDBusListener::loadJson(const QString &fileName) +{ + Q_ASSERT(!fileName.isEmpty()); +#define CHECK(cond, warning)\ + if (!cond) {\ + qCWarning(lcQpaThemeDBus) << fileName << warning << "Falling back to default.";\ + return;\ + } + +#define PARSE(var, enumeration, string)\ + enumeration var;\ + {\ + bool success;\ + const int val = QMetaEnum::fromType<enumeration>().keyToValue(string.toLatin1(), &success);\ + CHECK(success, "Parse Error: Invalid value" << string << "for" << #var);\ + var = static_cast<enumeration>(val);\ + } + + QFile file(fileName); + CHECK(file.exists(), fileName << "doesn't exist."); + CHECK(file.open(QIODevice::ReadOnly), "could not be opened for reading."); + + QJsonParseError error; + QJsonDocument doc = QJsonDocument::fromJson(file.readAll(), &error); + CHECK((error.error == QJsonParseError::NoError), error.errorString()); + CHECK(doc.isObject(), "Parse Error: Expected root object" << s_root); + + const QJsonObject &root = doc.object(); + CHECK(root.contains(s_root), "Parse Error: Expected root object" << s_root); + CHECK(root[s_root][s_signals].isArray(), "Parse Error: Expected array" << s_signals); + + const QJsonArray &sigs = root[s_root][s_signals].toArray(); + CHECK((sigs.count() > 0), "Parse Error: Found empty array" << s_signals); + + for (auto sig = sigs.constBegin(); sig != sigs.constEnd(); ++sig) { + CHECK(sig->isObject(), "Parse Error: Expected object array" << s_signals); + const QJsonObject &obj = sig->toObject(); + CHECK(obj.contains(s_dbusLocation), "Parse Error: Expected key" << s_dbusLocation); + CHECK(obj.contains(s_dbusKey), "Parse Error: Expected key" << s_dbusKey); + CHECK(obj.contains(s_provider), "Parse Error: Expected key" << s_provider); + CHECK(obj.contains(s_setting), "Parse Error: Expected key" << s_setting); + const QString &location = obj[s_dbusLocation].toString(); + const QString &key = obj[s_dbusKey].toString(); + const QString &providerString = obj[s_provider].toString(); + const QString &settingString = obj[s_setting].toString(); + PARSE(provider, Provider, providerString); + PARSE(setting, Setting, settingString); + const DBusKey dkey(location, key); + CHECK (!m_signalMap.contains(dkey), "Duplicate key" << location << key); + m_signalMap.insert(dkey, ChangeSignal(provider, setting)); + } +#undef PARSE +#undef CHECK + + if (m_signalMap.count() > 0) + qCInfo(lcQpaThemeDBus) << "Successfully imported" << fileName; + else + qCWarning(lcQpaThemeDBus) << "No data imported from" << fileName << "falling back to default."; + +#ifdef QT_DEBUG + const int count = m_signalMap.count(); + if (count == 0) + return; + + qCDebug(lcQpaThemeDBus) << "Listening to" << count << "signals:"; + for (auto it = m_signalMap.constBegin(); it != m_signalMap.constEnd(); ++it) { + qDebug() << it.key().key << it.key().location << "mapped to" + << it.value().provider << it.value().setting; + } + +#endif +} + +void QGenericUnixThemeDBusListener::saveJson(const QString &fileName) const +{ + Q_ASSERT(!m_signalMap.isEmpty()); + Q_ASSERT(!fileName.isEmpty()); + QFile file(fileName); + if (!file.open(QIODevice::WriteOnly)) { + qCWarning(lcQpaThemeDBus) << fileName << "could not be opened for writing."; + return; + } + + QJsonArray sigs; + for (auto sig = m_signalMap.constBegin(); sig != m_signalMap.constEnd(); ++sig) { + const DBusKey &dkey = sig.key(); + const ChangeSignal &csig = sig.value(); + QJsonObject obj; + obj[s_dbusLocation] = dkey.location; + obj[s_dbusKey] = dkey.key; + obj[s_provider] = QLatin1StringView(QMetaEnum::fromType<Provider>() + .valueToKey(static_cast<int>(csig.provider))); + obj[s_setting] = QLatin1StringView(QMetaEnum::fromType<Setting>() + .valueToKey(static_cast<int>(csig.setting))); + sigs.append(obj); + } + QJsonObject obj; + obj[s_signals] = sigs; + QJsonObject root; + root[s_root] = obj; + QJsonDocument doc(root); + file.write(doc.toJson()); + file.close(); +} + +void QGenericUnixThemeDBusListener::populateSignalMap() +{ + m_signalMap.clear(); + const QString &loadJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS"); + if (!loadJsonFile.isEmpty()) + loadJson(loadJsonFile); + if (!m_signalMap.isEmpty()) + return; + + m_signalMap.insert(DBusKey("org.kde.kdeglobals.KDE"_L1, "widgetStyle"_L1), + ChangeSignal(Provider::Kde, Setting::ApplicationStyle)); + + m_signalMap.insert(DBusKey("org.kde.kdeglobals.General"_L1, "ColorScheme"_L1), + ChangeSignal(Provider::Kde, Setting::Theme)); + + m_signalMap.insert(DBusKey("org.gnome.desktop.interface"_L1, "gtk-theme"_L1), + ChangeSignal(Provider::Gtk, Setting::Theme)); + + m_signalMap.insert(DBusKey("org.freedesktop.appearance"_L1, "color-scheme"_L1), + ChangeSignal(Provider::Gnome, Setting::ColorScheme)); + + const QString &saveJsonFile = qEnvironmentVariable("QT_QPA_DBUS_SIGNALS_SAVE"); + if (!saveJsonFile.isEmpty()) + saveJson(saveJsonFile); +} + +std::optional<QGenericUnixThemeDBusListener::ChangeSignal> + QGenericUnixThemeDBusListener::findSignal(const QString &location, const QString &key) const +{ + const DBusKey dkey(location, key); + std::optional<QGenericUnixThemeDBusListener::ChangeSignal> ret; + if (m_signalMap.contains(dkey)) + ret.emplace(m_signalMap.value(dkey)); + + return ret; } void QGenericUnixThemeDBusListener::onSettingChanged(const QString &location, const QString &key, const QDBusVariant &value) { - const SettingType type = toSettingType(location, key); - if (type != SettingType::Unknown) - emit settingChanged(type, value.variant().toString()); + auto sig = findSignal(location, key); + if (!sig.has_value()) + return; + + emit settingChanged(sig.value().provider, sig.value().setting, value.variant().toString()); } #endif //QT_NO_DBUS @@ -264,7 +473,7 @@ QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const { - if (isDBusTrayAvailable()) + if (shouldUseDBusTray()) return new QDBusTrayIcon(); return nullptr; } @@ -290,6 +499,10 @@ QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const return QVariant(int(X11KeyboardScheme)); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::MouseCursorTheme: + return QVariant(mouseCursorTheme()); + case QPlatformTheme::MouseCursorSize: + return QVariant(mouseCursorSize()); default: break; } @@ -325,6 +538,48 @@ class QKdeThemePrivate : public QPlatformThemePrivate { public: + enum class KdeSettingType { + Root, + KDE, + Icons, + ToolBarIcons, + ToolBarStyle, + Fonts, + Colors, + }; + + enum class KdeSetting { + WidgetStyle, + ColorScheme, + SingleClick, + ShowIconsOnPushButtons, + IconTheme, + ToolBarIconSize, + ToolButtonStyle, + WheelScrollLines, + DoubleClickInterval, + StartDragDistance, + StartDragTime, + CursorBlinkRate, + Font, + Fixed, + MenuFont, + ToolBarFont, + ButtonBackground, + WindowBackground, + ViewForeground, + WindowForeground, + ViewBackground, + SelectionBackground, + SelectionForeground, + ViewBackgroundAlternate, + ButtonForeground, + ViewForegroundLink, + ViewForegroundVisited, + TooltipBackground, + TooltipForeground, + }; + QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion); static QString kdeGlobals(const QString &kdeDir, int kdeVersion) @@ -335,7 +590,9 @@ public: } void refresh(); - static QVariant readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings); + static QVariant readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &settings); + QVariant readKdeSetting(KdeSetting s) const; + void clearKdeSettings() const; static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal); static QFont *kdeFont(const QVariant &fontValue); static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs); @@ -356,31 +613,38 @@ public: int startDragDist = 10; int startDragTime = 500; int cursorBlinkRate = 1000; - Qt::Appearance m_appearance = Qt::Appearance::Unknown; - void updateAppearance(const QString &themeName); + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; + void updateColorScheme(const QString &themeName); -#ifndef QT_NO_DBUS private: + mutable QHash<QString, QSettings *> kdeSettings; +#ifndef QT_NO_DBUS std::unique_ptr<QGenericUnixThemeDBusListener> dbus; bool initDbus(); - void settingChangedHandler(QGenericUnixThemeDBusListener::SettingType type, const QString &value); + void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value); #endif // QT_NO_DBUS }; #ifndef QT_NO_DBUS -void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::SettingType type, const QString &value) +void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value) { - switch (type) { - case QGenericUnixThemeDBusListener::SettingType::KdeGlobalTheme: + if (provider != QGenericUnixThemeDBusListener::Provider::Kde) + return; + + switch (setting) { + case QGenericUnixThemeDBusListener::Setting::ColorScheme: + qCDebug(lcQpaThemeDBus) << "KDE color theme changed to:" << value; + break; + case QGenericUnixThemeDBusListener::Setting::Theme: qCDebug(lcQpaThemeDBus) << "KDE global theme changed to:" << value; break; - case QGenericUnixThemeDBusListener::SettingType::KdeApplicationStyle: + case QGenericUnixThemeDBusListener::Setting::ApplicationStyle: qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value; break; - case QGenericUnixThemeDBusListener::SettingType::GtkTheme: - return; // KDE can change GTK2 / GTK3 themes. Ignored here, handled in GnomeTheme - case QGenericUnixThemeDBusListener::SettingType::Unknown: - Q_UNREACHABLE(); } refresh(); @@ -388,20 +652,17 @@ void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Sett bool QKdeThemePrivate::initDbus() { - static constexpr QLatin1StringView service(""); - static constexpr QLatin1StringView path("/org/freedesktop/portal/desktop"); - static constexpr QLatin1StringView interface("org.freedesktop.portal.Settings"); - static constexpr QLatin1StringView signal("SettingChanged"); - - dbus.reset(new QGenericUnixThemeDBusListener(service, path, interface, signal)); + dbus.reset(new QGenericUnixThemeDBusListener()); Q_ASSERT(dbus); // Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject - auto wrapper = [this](QGenericUnixThemeDBusListener::SettingType type, const QString &value) { - settingChangedHandler(type, value); + auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value) { + settingChangedHandler(provider, setting, value); }; - return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, wrapper); + return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper); } #endif // QT_NO_DBUS @@ -413,9 +674,136 @@ QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion) #endif // QT_NO_DBUS } +static constexpr QLatin1StringView settingsPrefix(QKdeThemePrivate::KdeSettingType type) +{ + switch (type) { + case QKdeThemePrivate::KdeSettingType::Root: + return QLatin1StringView(); + case QKdeThemePrivate::KdeSettingType::KDE: + return QLatin1StringView("KDE/"); + case QKdeThemePrivate::KdeSettingType::Fonts: + return QLatin1StringView(); + case QKdeThemePrivate::KdeSettingType::Colors: + return QLatin1StringView("Colors:"); + case QKdeThemePrivate::KdeSettingType::Icons: + return QLatin1StringView("Icons/"); + case QKdeThemePrivate::KdeSettingType::ToolBarIcons: + return QLatin1StringView("ToolbarIcons/"); + case QKdeThemePrivate::KdeSettingType::ToolBarStyle: + return QLatin1StringView("Toolbar style/"); + } + Q_UNREACHABLE_RETURN(QLatin1StringView()); +} + +static constexpr QKdeThemePrivate::KdeSettingType settingsType(QKdeThemePrivate::KdeSetting setting) +{ +#define CASE(s, type) case QKdeThemePrivate::KdeSetting::s:\ + return QKdeThemePrivate::KdeSettingType::type + + switch (setting) { + CASE(WidgetStyle, Root); + CASE(ColorScheme, Root); + CASE(SingleClick, KDE); + CASE(ShowIconsOnPushButtons, KDE); + CASE(IconTheme, Icons); + CASE(ToolBarIconSize, ToolBarIcons); + CASE(ToolButtonStyle, ToolBarStyle); + CASE(WheelScrollLines, KDE); + CASE(DoubleClickInterval, KDE); + CASE(StartDragDistance, KDE); + CASE(StartDragTime, KDE); + CASE(CursorBlinkRate, KDE); + CASE(Font, Root); + CASE(Fixed, Root); + CASE(MenuFont, Root); + CASE(ToolBarFont, Root); + CASE(ButtonBackground, Colors); + CASE(WindowBackground, Colors); + CASE(ViewForeground, Colors); + CASE(WindowForeground, Colors); + CASE(ViewBackground, Colors); + CASE(SelectionBackground, Colors); + CASE(SelectionForeground, Colors); + CASE(ViewBackgroundAlternate, Colors); + CASE(ButtonForeground, Colors); + CASE(ViewForegroundLink, Colors); + CASE(ViewForegroundVisited, Colors); + CASE(TooltipBackground, Colors); + CASE(TooltipForeground, Colors); + }; + Q_UNREACHABLE_RETURN(QKdeThemePrivate::KdeSettingType::Root); +} +#undef CASE + +static constexpr QLatin1StringView settingsKey(QKdeThemePrivate::KdeSetting setting) +{ + switch (setting) { + case QKdeThemePrivate::KdeSetting::WidgetStyle: + return QLatin1StringView("widgetStyle"); + case QKdeThemePrivate::KdeSetting::ColorScheme: + return QLatin1StringView("ColorScheme"); + case QKdeThemePrivate::KdeSetting::SingleClick: + return QLatin1StringView("SingleClick"); + case QKdeThemePrivate::KdeSetting::ShowIconsOnPushButtons: + return QLatin1StringView("ShowIconsOnPushButtons"); + case QKdeThemePrivate::KdeSetting::IconTheme: + return QLatin1StringView("Theme"); + case QKdeThemePrivate::KdeSetting::ToolBarIconSize: + return QLatin1StringView("Size"); + case QKdeThemePrivate::KdeSetting::ToolButtonStyle: + return QLatin1StringView("ToolButtonStyle"); + case QKdeThemePrivate::KdeSetting::WheelScrollLines: + return QLatin1StringView("WheelScrollLines"); + case QKdeThemePrivate::KdeSetting::DoubleClickInterval: + return QLatin1StringView("DoubleClickInterval"); + case QKdeThemePrivate::KdeSetting::StartDragDistance: + return QLatin1StringView("StartDragDist"); + case QKdeThemePrivate::KdeSetting::StartDragTime: + return QLatin1StringView("StartDragTime"); + case QKdeThemePrivate::KdeSetting::CursorBlinkRate: + return QLatin1StringView("CursorBlinkRate"); + case QKdeThemePrivate::KdeSetting::Font: + return QLatin1StringView("font"); + case QKdeThemePrivate::KdeSetting::Fixed: + return QLatin1StringView("fixed"); + case QKdeThemePrivate::KdeSetting::MenuFont: + return QLatin1StringView("menuFont"); + case QKdeThemePrivate::KdeSetting::ToolBarFont: + return QLatin1StringView("toolBarFont"); + case QKdeThemePrivate::KdeSetting::ButtonBackground: + return QLatin1StringView("Button/BackgroundNormal"); + case QKdeThemePrivate::KdeSetting::WindowBackground: + return QLatin1StringView("Window/BackgroundNormal"); + case QKdeThemePrivate::KdeSetting::ViewForeground: + return QLatin1StringView("View/ForegroundNormal"); + case QKdeThemePrivate::KdeSetting::WindowForeground: + return QLatin1StringView("Window/ForegroundNormal"); + case QKdeThemePrivate::KdeSetting::ViewBackground: + return QLatin1StringView("View/BackgroundNormal"); + case QKdeThemePrivate::KdeSetting::SelectionBackground: + return QLatin1StringView("Selection/BackgroundNormal"); + case QKdeThemePrivate::KdeSetting::SelectionForeground: + return QLatin1StringView("Selection/ForegroundNormal"); + case QKdeThemePrivate::KdeSetting::ViewBackgroundAlternate: + return QLatin1StringView("View/BackgroundAlternate"); + case QKdeThemePrivate::KdeSetting::ButtonForeground: + return QLatin1StringView("Button/ForegroundNormal"); + case QKdeThemePrivate::KdeSetting::ViewForegroundLink: + return QLatin1StringView("View/ForegroundLink"); + case QKdeThemePrivate::KdeSetting::ViewForegroundVisited: + return QLatin1StringView("View/ForegroundVisited"); + case QKdeThemePrivate::KdeSetting::TooltipBackground: + return QLatin1StringView("Tooltip/BackgroundNormal"); + case QKdeThemePrivate::KdeSetting::TooltipForeground: + return QLatin1StringView("Tooltip/ForegroundNormal"); + }; + Q_UNREACHABLE_RETURN(QLatin1StringView()); +} + void QKdeThemePrivate::refresh() { resources.clear(); + clearKdeSettings(); toolButtonStyle = Qt::ToolButtonTextBesideIcon; toolBarIconSize = 0; @@ -428,45 +816,39 @@ void QKdeThemePrivate::refresh() else iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen"); - QHash<QString, QSettings*> kdeSettings; - QPalette systemPalette = QPalette(); readKdeSystemPalette(kdeDirs, kdeVersion, kdeSettings, &systemPalette); resources.palettes[QPlatformTheme::SystemPalette] = new QPalette(systemPalette); //## TODO tooltip color - const QVariant styleValue = readKdeSetting(QStringLiteral("widgetStyle"), kdeDirs, kdeVersion, kdeSettings); + const QVariant styleValue = readKdeSetting(KdeSetting::WidgetStyle); if (styleValue.isValid()) { const QString style = styleValue.toString(); if (style != styleNames.front()) styleNames.push_front(style); } - const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs, - kdeVersion, kdeSettings); + const QVariant colorScheme = readKdeSetting(KdeSetting::ColorScheme); - if (colorScheme.isValid()) - updateAppearance(colorScheme.toString()); - else - m_appearance = Qt::Appearance::Unknown; + updateColorScheme(colorScheme.toString()); - const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings); + const QVariant singleClickValue = readKdeSetting(KdeSetting::SingleClick); if (singleClickValue.isValid()) singleClick = singleClickValue.toBool(); - const QVariant showIconsOnPushButtonsValue = readKdeSetting(QStringLiteral("KDE/ShowIconsOnPushButtons"), kdeDirs, kdeVersion, kdeSettings); + const QVariant showIconsOnPushButtonsValue = readKdeSetting(KdeSetting::ShowIconsOnPushButtons); if (showIconsOnPushButtonsValue.isValid()) showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool(); - const QVariant themeValue = readKdeSetting(QStringLiteral("Icons/Theme"), kdeDirs, kdeVersion, kdeSettings); + const QVariant themeValue = readKdeSetting(KdeSetting::IconTheme); if (themeValue.isValid()) iconThemeName = themeValue.toString(); - const QVariant toolBarIconSizeValue = readKdeSetting(QStringLiteral("ToolbarIcons/Size"), kdeDirs, kdeVersion, kdeSettings); + const QVariant toolBarIconSizeValue = readKdeSetting(KdeSetting::ToolBarIconSize); if (toolBarIconSizeValue.isValid()) toolBarIconSize = toolBarIconSizeValue.toInt(); - const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle"), kdeDirs, kdeVersion, kdeSettings); + const QVariant toolbarStyleValue = readKdeSetting(KdeSetting::ToolButtonStyle); if (toolbarStyleValue.isValid()) { const QString toolBarStyle = toolbarStyleValue.toString(); if (toolBarStyle == "TextBesideIcon"_L1) @@ -477,35 +859,35 @@ void QKdeThemePrivate::refresh() toolButtonStyle = Qt::ToolButtonTextUnderIcon; } - const QVariant wheelScrollLinesValue = readKdeSetting(QStringLiteral("KDE/WheelScrollLines"), kdeDirs, kdeVersion, kdeSettings); + const QVariant wheelScrollLinesValue = readKdeSetting(KdeSetting::WheelScrollLines); if (wheelScrollLinesValue.isValid()) wheelScrollLines = wheelScrollLinesValue.toInt(); - const QVariant doubleClickIntervalValue = readKdeSetting(QStringLiteral("KDE/DoubleClickInterval"), kdeDirs, kdeVersion, kdeSettings); + const QVariant doubleClickIntervalValue = readKdeSetting(KdeSetting::DoubleClickInterval); if (doubleClickIntervalValue.isValid()) doubleClickInterval = doubleClickIntervalValue.toInt(); - const QVariant startDragDistValue = readKdeSetting(QStringLiteral("KDE/StartDragDist"), kdeDirs, kdeVersion, kdeSettings); + const QVariant startDragDistValue = readKdeSetting(KdeSetting::StartDragDistance); if (startDragDistValue.isValid()) startDragDist = startDragDistValue.toInt(); - const QVariant startDragTimeValue = readKdeSetting(QStringLiteral("KDE/StartDragTime"), kdeDirs, kdeVersion, kdeSettings); + const QVariant startDragTimeValue = readKdeSetting(KdeSetting::StartDragTime); if (startDragTimeValue.isValid()) startDragTime = startDragTimeValue.toInt(); - const QVariant cursorBlinkRateValue = readKdeSetting(QStringLiteral("KDE/CursorBlinkRate"), kdeDirs, kdeVersion, kdeSettings); + const QVariant cursorBlinkRateValue = readKdeSetting(KdeSetting::CursorBlinkRate); if (cursorBlinkRateValue.isValid()) { cursorBlinkRate = cursorBlinkRateValue.toInt(); cursorBlinkRate = cursorBlinkRate > 0 ? qBound(200, cursorBlinkRate, 2000) : 0; } // Read system font, ignore 'smallestReadableFont' - if (QFont *systemFont = kdeFont(readKdeSetting(QStringLiteral("font"), kdeDirs, kdeVersion, kdeSettings))) + if (QFont *systemFont = kdeFont(readKdeSetting(KdeSetting::Font))) resources.fonts[QPlatformTheme::SystemFont] = systemFont; else resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1StringView(defaultSystemFontNameC), defaultSystemFontSize); - if (QFont *fixedFont = kdeFont(readKdeSetting(QStringLiteral("fixed"), kdeDirs, kdeVersion, kdeSettings))) { + if (QFont *fixedFont = kdeFont(readKdeSetting(KdeSetting::Fixed))) { resources.fonts[QPlatformTheme::FixedFont] = fixedFont; } else { fixedFont = new QFont(QLatin1StringView(defaultFixedFontNameC), defaultSystemFontSize); @@ -513,12 +895,12 @@ void QKdeThemePrivate::refresh() resources.fonts[QPlatformTheme::FixedFont] = fixedFont; } - if (QFont *menuFont = kdeFont(readKdeSetting(QStringLiteral("menuFont"), kdeDirs, kdeVersion, kdeSettings))) { + if (QFont *menuFont = kdeFont(readKdeSetting(KdeSetting::MenuFont))) { resources.fonts[QPlatformTheme::MenuFont] = menuFont; resources.fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont); } - if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings))) + if (QFont *toolBarFont = kdeFont(readKdeSetting(KdeSetting::ToolBarFont))) resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont; QWindowSystemInterface::handleThemeChange(); @@ -528,7 +910,7 @@ void QKdeThemePrivate::refresh() qDeleteAll(kdeSettings); } -QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings) +QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s, const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings) { for (const QString &kdeDir : kdeDirs) { QSettings *settings = kdeSettings.value(kdeDir); @@ -540,6 +922,7 @@ QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList } } if (settings) { + const QString key = settingsPrefix(settingsType(s)) + settingsKey(s); const QVariant value = settings->value(key); if (value.isValid()) return value; @@ -548,6 +931,16 @@ QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList return QVariant(); } +QVariant QKdeThemePrivate::readKdeSetting(KdeSetting s) const +{ + return readKdeSetting(s, kdeDirs, kdeVersion, kdeSettings); +} + +void QKdeThemePrivate::clearKdeSettings() const +{ + kdeSettings.clear(); +} + // Reads the color from the KDE configuration, and store it in the // palette with the given color role if found. static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVariant &value) @@ -563,7 +956,7 @@ static inline bool kdeColor(QPalette *pal, QPalette::ColorRole role, const QVari void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash<QString, QSettings*> &kdeSettings, QPalette *pal) { - if (!kdeColor(pal, QPalette::Button, readKdeSetting(QStringLiteral("Colors:Button/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings))) { + if (!kdeColor(pal, QPalette::Button, readKdeSetting(KdeSetting::ButtonBackground, kdeDirs, kdeVersion, kdeSettings))) { // kcolorscheme.cpp: SetDefaultColors const QColor defaultWindowBackground(214, 210, 208); const QColor defaultButtonBackground(223, 220, 217); @@ -571,18 +964,18 @@ void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeV return; } - kdeColor(pal, QPalette::Window, readKdeSetting(QStringLiteral("Colors:Window/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::Text, readKdeSetting(QStringLiteral("Colors:View/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::WindowText, readKdeSetting(QStringLiteral("Colors:Window/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::Base, readKdeSetting(QStringLiteral("Colors:View/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::Highlight, readKdeSetting(QStringLiteral("Colors:Selection/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::HighlightedText, readKdeSetting(QStringLiteral("Colors:Selection/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::AlternateBase, readKdeSetting(QStringLiteral("Colors:View/BackgroundAlternate"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::ButtonText, readKdeSetting(QStringLiteral("Colors:Button/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::Link, readKdeSetting(QStringLiteral("Colors:View/ForegroundLink"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::LinkVisited, readKdeSetting(QStringLiteral("Colors:View/ForegroundVisited"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(QStringLiteral("Colors:Tooltip/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings)); - kdeColor(pal, QPalette::ToolTipText, readKdeSetting(QStringLiteral("Colors:Tooltip/ForegroundNormal"), kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::Window, readKdeSetting(KdeSetting::WindowBackground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::Text, readKdeSetting(KdeSetting::ViewForeground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::WindowText, readKdeSetting(KdeSetting::WindowForeground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::Base, readKdeSetting(KdeSetting::ViewBackground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::Highlight, readKdeSetting(KdeSetting::SelectionBackground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::HighlightedText, readKdeSetting(KdeSetting::SelectionForeground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::AlternateBase, readKdeSetting(KdeSetting::ViewBackgroundAlternate, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::ButtonText, readKdeSetting(KdeSetting::ButtonForeground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::Link, readKdeSetting(KdeSetting::ViewForegroundLink, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::LinkVisited, readKdeSetting(KdeSetting::ViewForegroundVisited, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::ToolTipBase, readKdeSetting(KdeSetting::TooltipBackground, kdeDirs, kdeVersion, kdeSettings)); + kdeColor(pal, QPalette::ToolTipText, readKdeSetting(KdeSetting::TooltipForeground, kdeDirs, kdeVersion, kdeSettings)); // The above code sets _all_ color roles to "normal" colors. In KDE, the disabled // color roles are calculated by applying various effects described in kdeglobals. @@ -711,6 +1104,10 @@ QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const return QVariant(d->cursorBlinkRate); case QPlatformTheme::UiEffects: return QVariant(int(HoverEffect)); + case QPlatformTheme::MouseCursorTheme: + return QVariant(mouseCursorTheme()); + case QPlatformTheme::MouseCursorSize: + return QVariant(mouseCursorSize()); default: break; } @@ -727,48 +1124,47 @@ QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions #endif } -Qt::Appearance QKdeTheme::appearance() const +Qt::ColorScheme QKdeTheme::colorScheme() const { - return d_func()->m_appearance; + return d_func()->m_colorScheme; } /*! \internal - \brief QKdeTheme::setAppearance - guess and set appearance for unix themes. - KDE themes do not have an appearance property. - The key words "dark" or "light" should be part of the theme name. + \brief QKdeTheme::updateColorScheme - guess and set a color scheme for unix themes. + KDE themes do not have a color scheme property. + The key words "dark" or "light" are usually part of the theme name. This is, however, not a mandatory convention. - If \param themeName contains a key word, the respective appearance is set. - If it doesn't, the appearance is heuristically determined by comparing text and base color + If \param themeName contains a valid key word, the respective color scheme is set. + If it doesn't, the color scheme is heuristically determined by comparing text and base color of the system palette. */ -void QKdeThemePrivate::updateAppearance(const QString &themeName) +void QKdeThemePrivate::updateColorScheme(const QString &themeName) { if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) { - m_appearance = Qt::Appearance::Light; + m_colorScheme = Qt::ColorScheme::Light; return; } if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) { - m_appearance = Qt::Appearance::Dark; + m_colorScheme = Qt::ColorScheme::Dark; return; } if (systemPalette) { if (systemPalette->text().color().lightness() < systemPalette->base().color().lightness()) { - m_appearance = Qt::Appearance::Light; + m_colorScheme = Qt::ColorScheme::Light; return; } if (systemPalette->text().color().lightness() > systemPalette->base().color().lightness()) { - m_appearance = Qt::Appearance::Dark; + m_colorScheme = Qt::ColorScheme::Dark; return; } } - m_appearance = Qt::Appearance::Unknown; + m_colorScheme = Qt::ColorScheme::Unknown; } - const QPalette *QKdeTheme::palette(Palette type) const { Q_D(const QKdeTheme); @@ -848,7 +1244,7 @@ QPlatformMenuBar *QKdeTheme::createPlatformMenuBar() const #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const { - if (isDBusTrayAvailable()) + if (shouldUseDBusTray()) return new QDBusTrayIcon(); return nullptr; } @@ -889,11 +1285,11 @@ public: mutable QFont *fixedFont = nullptr; #ifndef QT_NO_DBUS - Qt::Appearance m_appearance = Qt::Appearance::Unknown; + Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown; private: std::unique_ptr<QGenericUnixThemeDBusListener> dbus; bool initDbus(); - void updateAppearance(const QString &themeName); + void updateColorScheme(const QString &themeName); #endif // QT_NO_DBUS }; @@ -914,35 +1310,37 @@ QGnomeThemePrivate::~QGnomeThemePrivate() #ifndef QT_NO_DBUS bool QGnomeThemePrivate::initDbus() { - static constexpr QLatin1StringView service(""); - static constexpr QLatin1StringView path("/org/freedesktop/portal/desktop"); - static constexpr QLatin1StringView interface("org.freedesktop.portal.Settings"); - static constexpr QLatin1StringView signal("SettingChanged"); - dbus.reset(new QGenericUnixThemeDBusListener(service, path, interface, signal)); + dbus.reset(new QGenericUnixThemeDBusListener()); Q_ASSERT(dbus); // Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject - auto wrapper = [this](QGenericUnixThemeDBusListener::SettingType type, const QString &value) { - if (type == QGenericUnixThemeDBusListener::SettingType::GtkTheme) - updateAppearance(value); - }; + auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider, + QGenericUnixThemeDBusListener::Setting setting, + const QString &value) { + if (provider != QGenericUnixThemeDBusListener::Provider::Gnome + && provider != QGenericUnixThemeDBusListener::Provider::Gtk) { + return; + } - return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, wrapper); + if (setting == QGenericUnixThemeDBusListener::Setting::Theme) + updateColorScheme(value); + }; + return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper); } -void QGnomeThemePrivate::updateAppearance(const QString &themeName) +void QGnomeThemePrivate::updateColorScheme(const QString &themeName) { - const auto oldAppearance = m_appearance; + const auto oldColorScheme = m_colorScheme; if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) { - m_appearance = Qt::Appearance::Light; + m_colorScheme = Qt::ColorScheme::Light; } else if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) { - m_appearance = Qt::Appearance::Dark; + m_colorScheme = Qt::ColorScheme::Dark; } else { - m_appearance = Qt::Appearance::Unknown; + m_colorScheme = Qt::ColorScheme::Unknown; } - if (oldAppearance != m_appearance) + if (oldColorScheme != m_colorScheme) QWindowSystemInterface::handleThemeChange(); } #endif // QT_NO_DBUS @@ -983,6 +1381,10 @@ QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const QList<Qt::Key>({ Qt::Key_Space, Qt::Key_Return, Qt::Key_Enter, Qt::Key_Select })); case QPlatformTheme::PreselectFirstFileInDirectory: return true; + case QPlatformTheme::MouseCursorTheme: + return QVariant(mouseCursorTheme()); + case QPlatformTheme::MouseCursorSize: + return QVariant(mouseCursorSize()); default: break; } @@ -1027,9 +1429,9 @@ QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const return nullptr; } -Qt::Appearance QGnomeTheme::appearance() const +Qt::ColorScheme QGnomeTheme::colorScheme() const { - return d_func()->m_appearance; + return d_func()->m_colorScheme; } #endif @@ -1037,7 +1439,7 @@ Qt::Appearance QGnomeTheme::appearance() const #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const { - if (isDBusTrayAvailable()) + if (shouldUseDBusTray()) return new QDBusTrayIcon(); return nullptr; } diff --git a/src/gui/platform/unix/qgenericunixthemes_p.h b/src/gui/platform/unix/qgenericunixthemes_p.h index ed60f9484c..63b20651e6 100644 --- a/src/gui/platform/unix/qgenericunixthemes_p.h +++ b/src/gui/platform/unix/qgenericunixthemes_p.h @@ -77,7 +77,7 @@ public: QPlatformTheme::IconOptions iconOptions = { }) const override; const QPalette *palette(Palette type = SystemPalette) const override; - Qt::Appearance appearance() const override; + Qt::ColorScheme colorScheme() const override; const QFont *font(Font type) const override; #ifndef QT_NO_DBUS @@ -107,7 +107,7 @@ public: virtual QString gtkFontName() const; #ifndef QT_NO_DBUS QPlatformMenuBar *createPlatformMenuBar() const override; - Qt::Appearance appearance() const override; + Qt::ColorScheme colorScheme() const override; #endif #if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; diff --git a/src/gui/platform/unix/qunixnativeinterface.cpp b/src/gui/platform/unix/qunixnativeinterface.cpp index 60a96932e6..09561d9ada 100644 --- a/src/gui/platform/unix/qunixnativeinterface.cpp +++ b/src/gui/platform/unix/qunixnativeinterface.cpp @@ -123,6 +123,22 @@ QOpenGLContext *QNativeInterface::QGLXContext::fromNative(GLXContext visualBased \return the EGLDisplay associated with the underlying EGLContext. */ + +/*! + \fn void QNativeInterface::QEGLContext::invalidateContext() + \since 6.5 + \brief Marks the context as invalid + + If this context is used by the Qt Quick scenegraph, this will trigger the + SceneGraph to destroy this context and create a new one. + + Similarly to QPlatformWindow::invalidateSurface(), + this function can only be expected to have an effect on certain platforms, + such as eglfs. + + \sa QOpenGLContext::isValid(), QPlatformWindow::invalidateSurface() +*/ + QT_DEFINE_NATIVE_INTERFACE(QEGLContext); QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEGLIntegration); @@ -215,4 +231,81 @@ QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QEvdevKeyMapper); #endif // QT_CONFIG(evdev) +#if QT_CONFIG(wayland) + +/*! + \class QNativeInterface::QWaylandApplication + \inheaderfile QGuiApplication + \since 6.5 + \brief Native interface to a Wayland application. + + Accessed through QGuiApplication::nativeInterface(). + \inmodule QtGui + \ingroup native-interfaces + \ingroup native-interfaces-qguiapplication +*/ +/*! + \fn wl_display *QNativeInterface::QWaylandApplication::display() const + \return the wl_display that the application is using. +*/ +/*! + \fn wl_compositor *QNativeInterface::QWaylandApplication::compositor() const + \return the wl_compositor that the application is using. +*/ +/*! + \fn wl_keyboard *QNativeInterface::QWaylandApplication::keyboard() const + \return the wl_keyboard belonging to seat() if available. +*/ +/*! + \fn wl_pointer *QNativeInterface::QWaylandApplication::pointer() const + \return the wl_pointer belonging to seat() if available. +*/ +/*! + \fn wl_touch *QNativeInterface::QWaylandApplication::touch() const + \return the wl_touch belonging to seat() if available. +*/ +/*! + \fn uint *QNativeInterface::QWaylandApplication::lastInputSerial() const + \return the serial of the last input event on any seat. +*/ +/*! + \fn wl_seat *QNativeInterface::QWaylandApplication::lastInputSeat() const + \return the seat on which the last input event happened. +*/ +/*! + \fn wl_seat *QNativeInterface::QWaylandApplication::seat() const + \return the seat associated with the default input device. +*/ + +QT_DEFINE_NATIVE_INTERFACE(QWaylandApplication); + +/*! + \class QNativeInterface::QWaylandScreen + \since 6.7 + \brief Native interface to a screen on Wayland. + + Accessed through QScreen::nativeInterface(). + \inmodule QtGui + \ingroup native-interfaces + \ingroup native-interfaces-qscreen +*/ +/*! + \fn wl_output *QNativeInterface::QWaylandScreen::output() const + \return the underlying wl_output of this QScreen. +*/ +QT_DEFINE_NATIVE_INTERFACE(QWaylandScreen); + +/*! + \class QNativeInterface::QWaylandWindow + \since 6.5 + \internal + \brief Native interface to a Wayland window. + \inmodule QtGui + \ingroup native-interfaces +*/ + +QT_DEFINE_PRIVATE_NATIVE_INTERFACE(QWaylandWindow); + +#endif // QT_CONFIG(wayland) + QT_END_NAMESPACE diff --git a/src/gui/platform/unix/qxkbcommon.cpp b/src/gui/platform/unix/qxkbcommon.cpp index b3ee0f4948..ed29db3005 100644 --- a/src/gui/platform/unix/qxkbcommon.cpp +++ b/src/gui/platform/unix/qxkbcommon.cpp @@ -17,8 +17,6 @@ QT_BEGIN_NAMESPACE -Q_LOGGING_CATEGORY(lcXkbcommon, "qt.xkbcommon") - static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers, xkb_state *state, xkb_keycode_t code, bool superAsMeta, bool hyperAsMeta); @@ -239,10 +237,14 @@ static constexpr const auto KeyTbl = qMakeArray( Xkb2Qt<XKB_KEY_dead_small_schwa, Qt::Key_Dead_Small_Schwa>, Xkb2Qt<XKB_KEY_dead_capital_schwa, Qt::Key_Dead_Capital_Schwa>, Xkb2Qt<XKB_KEY_dead_greek, Qt::Key_Dead_Greek>, +/* The following four XKB_KEY_dead keys got removed in libxkbcommon 1.6.0 + The define check is kind of version check here. */ +#ifdef XKB_KEY_dead_lowline Xkb2Qt<XKB_KEY_dead_lowline, Qt::Key_Dead_Lowline>, Xkb2Qt<XKB_KEY_dead_aboveverticalline, Qt::Key_Dead_Aboveverticalline>, Xkb2Qt<XKB_KEY_dead_belowverticalline, Qt::Key_Dead_Belowverticalline>, Xkb2Qt<XKB_KEY_dead_longsolidusoverlay, Qt::Key_Dead_Longsolidusoverlay>, +#endif // Special keys from X.org - This include multimedia keys, // wireless/bluetooth/uwb keys, special launcher keys, etc. @@ -298,6 +300,7 @@ static constexpr const auto KeyTbl = qMakeArray( Xkb2Qt<XKB_KEY_XF86Book, Qt::Key_Book>, Xkb2Qt<XKB_KEY_XF86CD, Qt::Key_CD>, Xkb2Qt<XKB_KEY_XF86Calculater, Qt::Key_Calculator>, + Xkb2Qt<XKB_KEY_XF86Calculator, Qt::Key_Calculator>, Xkb2Qt<XKB_KEY_XF86Clear, Qt::Key_Clear>, Xkb2Qt<XKB_KEY_XF86ClearGrab, Qt::Key_ClearGrab>, Xkb2Qt<XKB_KEY_XF86Close, Qt::Key_Close>, @@ -461,7 +464,7 @@ QList<xkb_keysym_t> QXkbCommon::toKeysym(QKeyEvent *event) // From libxkbcommon keysym-utf.c: // "We allow to represent any UCS character in the range U-00000000 to // U-00FFFFFF by a keysym value in the range 0x01000000 to 0x01ffffff." - for (uint utf32 : qAsConst(ucs4)) + for (uint utf32 : std::as_const(ucs4)) keysyms.append(utf32 | 0x01000000); return keysyms; @@ -512,13 +515,13 @@ static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers mod // numeric keypad keys qtKey = Qt::Key_0 + (keysym - XKB_KEY_KP_0); } else if (QXkbCommon::isLatin1(keysym)) { - // Upper-case first, since Qt::Keys are defined in terms of their - // upper-case versions. + // Most Qt::Key values are determined by their upper-case version, + // where this is in the Latin-1 repertoire. So start with that: qtKey = QXkbCommon::qxkbcommon_xkb_keysym_to_upper(keysym); - // Upper-casing a Latin1 character might move it out of Latin1 range, - // for example U+00B5 MICRO SIGN, which upper-case equivalent is - // U+039C GREEK CAPITAL LETTER MU. If that's the case, then map the - // original lower-case character. + // However, Key_mu and Key_ydiaeresis are U+00B5 MICRO SIGN and + // U+00FF LATIN SMALL LETTER Y WITH DIAERESIS, both lower-case, + // with upper-case forms outside Latin-1, so use them as they are + // since they're the Qt::Key values. if (!QXkbCommon::isLatin1(qtKey)) qtKey = keysym; } else { @@ -564,7 +567,7 @@ static int keysymToQtKey_internal(xkb_keysym_t keysym, Qt::KeyboardModifiers mod return qtKey; } -Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state) +Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state, xkb_keysym_t keysym) { Qt::KeyboardModifiers modifiers = Qt::NoModifier; @@ -577,6 +580,9 @@ Qt::KeyboardModifiers QXkbCommon::modifiers(struct xkb_state *state) if (xkb_state_mod_name_is_active(state, XKB_MOD_NAME_LOGO, XKB_STATE_MODS_EFFECTIVE) > 0) modifiers |= Qt::MetaModifier; + if (isKeypad(keysym)) + modifiers |= Qt::KeypadModifier; + return modifiers; } @@ -593,10 +599,24 @@ static const Qt::KeyboardModifiers ModsTbl[] = { Qt::NoModifier // Fall-back to raw Key_*, for non-latin1 kb layouts }; +/* + Compatibility until all sub modules have transitioned to new API below +*/ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event, bool superAsMeta, bool hyperAsMeta) { QList<int> result; + auto keyCombinations = possibleKeyCombinations(state, event, superAsMeta, hyperAsMeta); + for (auto keyCombination : keyCombinations) + result << keyCombination.toCombined(); + + return result; +} + +QList<QKeyCombination> QXkbCommon::possibleKeyCombinations(xkb_state *state, const QKeyEvent *event, + bool superAsMeta, bool hyperAsMeta) +{ + QList<QKeyCombination> result; quint32 keycode = event->nativeScanCode(); if (!keycode) return result; @@ -610,7 +630,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event, ScopedXKBState scopedXkbQueryState(xkb_state_new(keymap)); xkb_state *queryState = scopedXkbQueryState.get(); if (!queryState) { - qCWarning(lcXkbcommon) << Q_FUNC_INFO << "failed to compile xkb keymap"; + qCWarning(lcQpaKeyMapper) << Q_FUNC_INFO << "failed to compile xkb keymap"; return result; } // get kb state from the master state and update the temporary state @@ -636,7 +656,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event, int baseQtKey = keysymToQtKey_internal(sym, modifiers, queryState, keycode, superAsMeta, hyperAsMeta); if (baseQtKey) - result += (baseQtKey + int(modifiers)); + result += QKeyCombination::fromCombined(baseQtKey + int(modifiers)); xkb_mod_index_t shiftMod = xkb_keymap_mod_get_index(keymap, "Shift"); xkb_mod_index_t altMod = xkb_keymap_mod_get_index(keymap, "Alt"); @@ -682,8 +702,9 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event, // catch only more specific shortcuts, i.e. Ctrl+Shift+= also generates Ctrl++ and +, // but Ctrl++ is more specific than +, so we should skip the last one bool ambiguous = false; - for (int shortcut : qAsConst(result)) { - if (int(shortcut & ~Qt::KeyboardModifierMask) == qtKey && (shortcut & mods) == mods) { + for (auto keyCombination : std::as_const(result)) { + if (keyCombination.key() == qtKey + && (keyCombination.keyboardModifiers() & mods) == mods) { ambiguous = true; break; } @@ -691,7 +712,7 @@ QList<int> QXkbCommon::possibleKeys(xkb_state *state, const QKeyEvent *event, if (ambiguous) continue; - result += (qtKey + int(mods)); + result += QKeyCombination::fromCombined(qtKey + int(mods)); } } @@ -723,13 +744,15 @@ void QXkbCommon::verifyHasLatinLayout(xkb_keymap *keymap) // selected layouts is irrelevant. Properly functioning desktop environments // handle this behind the scenes, even if no latin key based layout has been // explicitly listed in the selected layouts. - qCDebug(lcXkbcommon, "no keyboard layouts with latin keys present"); + qCDebug(lcQpaKeyMapper, "no keyboard layouts with latin keys present"); } xkb_keysym_t QXkbCommon::lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode) { xkb_layout_index_t layout; xkb_keysym_t sym = XKB_KEY_NoSymbol; + if (!state) + return sym; xkb_keymap *keymap = xkb_state_get_keymap(state); const xkb_layout_index_t layoutCount = xkb_keymap_num_layouts_for_key(keymap, keycode); const xkb_layout_index_t currentLayout = xkb_state_key_get_layout(state, keycode); @@ -795,7 +818,7 @@ void QXkbCommon::setXkbContext(QPlatformInputContext *inputContext, struct xkb_c QMetaMethod method = inputContext->metaObject()->method(methodIndex); Q_ASSERT(method.isValid()); if (!method.isValid()) - qCWarning(lcXkbcommon) << normalizedSignature << "not found on" << inputContextClassName; + qCWarning(lcQpaKeyMapper) << normalizedSignature << "not found on" << inputContextClassName; return method; }(); diff --git a/src/gui/platform/unix/qxkbcommon_p.h b/src/gui/platform/unix/qxkbcommon_p.h index adc96b2ad4..a40d794451 100644 --- a/src/gui/platform/unix/qxkbcommon_p.h +++ b/src/gui/platform/unix/qxkbcommon_p.h @@ -23,12 +23,12 @@ #include <xkbcommon/xkbcommon.h> +#include <qpa/qplatformkeymapper.h> + #include <memory> QT_BEGIN_NAMESPACE -Q_DECLARE_LOGGING_CATEGORY(lcXkbcommon) - class QEvent; class QKeyEvent; class QPlatformInputContext; @@ -44,26 +44,67 @@ public: static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers); static int keysymToQtKey(xkb_keysym_t keysym, Qt::KeyboardModifiers modifiers, xkb_state *state, xkb_keycode_t code, - bool superAsMeta = false, bool hyperAsMeta = false); + bool superAsMeta = true, bool hyperAsMeta = true); // xkbcommon_* API is part of libxkbcommon internals, with modifications as // described in the header of the implementation file. static void xkbcommon_XConvertCase(xkb_keysym_t sym, xkb_keysym_t *lower, xkb_keysym_t *upper); static xkb_keysym_t qxkbcommon_xkb_keysym_to_upper(xkb_keysym_t ks); - static Qt::KeyboardModifiers modifiers(struct xkb_state *state); + static Qt::KeyboardModifiers modifiers(struct xkb_state *state, xkb_keysym_t keysym = XKB_KEY_VoidSymbol); - static QList<int> possibleKeys(xkb_state *state, const QKeyEvent *event, - bool superAsMeta = false, bool hyperAsMeta = false); + static QList<int> possibleKeys(xkb_state *state, + const QKeyEvent *event, bool superAsMeta = false, bool hyperAsMeta = false); + static QList<QKeyCombination> possibleKeyCombinations(xkb_state *state, + const QKeyEvent *event, bool superAsMeta = false, bool hyperAsMeta = false); static void verifyHasLatinLayout(xkb_keymap *keymap); static xkb_keysym_t lookupLatinKeysym(xkb_state *state, xkb_keycode_t keycode); static bool isLatin1(xkb_keysym_t sym) { - return sym <= 0xff; + return sym >= 0x20 && sym <= 0xff; } static bool isKeypad(xkb_keysym_t sym) { - return sym >= XKB_KEY_KP_Space && sym <= XKB_KEY_KP_9; + switch (sym) { + case XKB_KEY_KP_Space: + case XKB_KEY_KP_Tab: + case XKB_KEY_KP_Enter: + case XKB_KEY_KP_F1: + case XKB_KEY_KP_F2: + case XKB_KEY_KP_F3: + case XKB_KEY_KP_F4: + case XKB_KEY_KP_Home: + case XKB_KEY_KP_Left: + case XKB_KEY_KP_Up: + case XKB_KEY_KP_Right: + case XKB_KEY_KP_Down: + case XKB_KEY_KP_Prior: + case XKB_KEY_KP_Next: + case XKB_KEY_KP_End: + case XKB_KEY_KP_Begin: + case XKB_KEY_KP_Insert: + case XKB_KEY_KP_Delete: + case XKB_KEY_KP_Equal: + case XKB_KEY_KP_Multiply: + case XKB_KEY_KP_Add: + case XKB_KEY_KP_Separator: + case XKB_KEY_KP_Subtract: + case XKB_KEY_KP_Decimal: + case XKB_KEY_KP_Divide: + case XKB_KEY_KP_0: + case XKB_KEY_KP_1: + case XKB_KEY_KP_2: + case XKB_KEY_KP_3: + case XKB_KEY_KP_4: + case XKB_KEY_KP_5: + case XKB_KEY_KP_6: + case XKB_KEY_KP_7: + case XKB_KEY_KP_8: + case XKB_KEY_KP_9: + return true; + default: + return false; + } } static void setXkbContext(QPlatformInputContext *inputContext, struct xkb_context *context); |