summaryrefslogtreecommitdiffstats
path: root/src/gui/platform/unix
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/platform/unix')
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp10
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h4
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h2
-rw-r--r--src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp18
-rw-r--r--src/gui/platform/unix/dbustray/qdbustrayicon.cpp12
-rw-r--r--src/gui/platform/unix/dbustray/qdbustraytypes.cpp6
-rw-r--r--src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp6
-rw-r--r--src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h4
-rw-r--r--src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h2
-rw-r--r--src/gui/platform/unix/qgenericunixservices.cpp361
-rw-r--r--src/gui/platform/unix/qgenericunixservices_p.h6
-rw-r--r--src/gui/platform/unix/qgenericunixthemes.cpp516
-rw-r--r--src/gui/platform/unix/qgenericunixthemes_p.h2
-rw-r--r--src/gui/platform/unix/qunixnativeinterface.cpp97
-rw-r--r--src/gui/platform/unix/qxkbcommon.cpp57
-rw-r--r--src/gui/platform/unix/qxkbcommon_p.h57
16 files changed, 1048 insertions, 112 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/qdbusmenuregistrarproxy_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h
index 4568fd2aa4..8041f3af0a 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h
@@ -63,7 +63,7 @@ public Q_SLOTS: // METHODS
{
QDBusMessage reply = call(QDBus::Block, QStringLiteral("GetMenuForWindow"), windowId);
QList<QVariant> arguments = reply.arguments();
- if (reply.type() == QDBusMessage::ReplyMessage && arguments.count() == 2)
+ if (reply.type() == QDBusMessage::ReplyMessage && arguments.size() == 2)
menuObjectPath = qdbus_cast<QDBusObjectPath>(arguments.at(1));
return reply;
}
diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
index f70ce67d4e..b7fd035883 100644
--- a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
+++ b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp
@@ -204,7 +204,7 @@ QString QDBusMenuItem::convertMnemonic(const QString &label)
// convert only the first occurrence of ampersand which is not at the end
// dbusmenu uses underscore instead of ampersand
int idx = label.indexOf(u'&');
- if (idx < 0 || idx == label.length() - 1)
+ if (idx < 0 || idx == label.size() - 1)
return label;
QString ret(label);
ret[idx] = u'_';
@@ -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)
@@ -271,7 +271,7 @@ QDebug operator<<(QDebug d, const QDBusMenuLayoutItem &item)
{
QDebugStateSaver saver(d);
d.nospace();
- d << "QDBusMenuLayoutItem(id=" << item.m_id << ", properties=" << item.m_properties << ", " << item.m_children.count() << " children)";
+ d << "QDBusMenuLayoutItem(id=" << item.m_id << ", properties=" << item.m_properties << ", " << item.m_children.size() << " children)";
return d;
}
#endif
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/dbustray/qstatusnotifieritemadaptor.cpp b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp
index 27a14e408a..2f6c13b6cf 100644
--- a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp
+++ b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp
@@ -136,6 +136,12 @@ void QStatusNotifierItemAdaptor::ContextMenu(int x, int y)
emit m_trayIcon->activated(QPlatformSystemTrayIcon::Context);
}
+void QStatusNotifierItemAdaptor::ProvideXdgActivationToken(const QString &token)
+{
+ qCDebug(qLcTray) << token;
+ qputenv("XDG_ACTIVATION_TOKEN", token.toUtf8());
+}
+
void QStatusNotifierItemAdaptor::Scroll(int w, const QString &s)
{
qCDebug(qLcTray) << w << s;
diff --git a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h
index 286aafa9bb..103fc974dd 100644
--- a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h
+++ b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h
@@ -73,6 +73,9 @@ class QStatusNotifierItemAdaptor: public QDBusAbstractAdaptor
" <property access=\"read\" type=\"(sa(iiay)ss)\" name=\"ToolTip\">\n"
" <annotation value=\"QXdgDBusToolTipStruct\" name=\"org.qtproject.QtDBus.QtTypeName\"/>\n"
" </property>\n"
+" <method name=\"ProvideXdgActivationToken\">\n"
+" <arg name=\"token\" type=\"s\" direction=\"in\"/>\n"
+" </method>\n"
" <method name=\"ContextMenu\">\n"
" <arg direction=\"in\" type=\"i\" name=\"x\"/>\n"
" <arg direction=\"in\" type=\"i\" name=\"y\"/>\n"
@@ -150,6 +153,7 @@ public: // PROPERTIES
public Q_SLOTS: // METHODS
void Activate(int x, int y);
void ContextMenu(int x, int y);
+ void ProvideXdgActivationToken(const QString &token);
void Scroll(int delta, const QString &orientation);
void SecondaryActivate(int x, int y);
Q_SIGNALS: // SIGNALS
diff --git a/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h b/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h
index fe61723bd2..dfbc64f33b 100644
--- a/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h
+++ b/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h
@@ -77,7 +77,7 @@ public Q_SLOTS: // METHODS
inline QDBusReply<QString> getServerInformation(QString &vendor, QString &version, QString &specVersion)
{
QDBusMessage reply = call(QDBus::Block, QStringLiteral("GetServerInformation"));
- if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 4) {
+ if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().size() == 4) {
vendor = qdbus_cast<QString>(reply.arguments().at(1));
version = qdbus_cast<QString>(reply.arguments().at(2));
specVersion = qdbus_cast<QString>(reply.arguments().at(3));
diff --git a/src/gui/platform/unix/qgenericunixservices.cpp b/src/gui/platform/unix/qgenericunixservices.cpp
index 54a0406cab..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,
@@ -257,71 +280,261 @@ static inline QDBusMessage xdgDesktopPortalSendEmail(const QUrl &url, const QStr
return QDBusConnection::sessionBus().call(message);
}
+
+namespace {
+struct XDGDesktopColor
+{
+ double r = 0;
+ double g = 0;
+ double b = 0;
+
+ QColor toQColor() const
+ {
+ constexpr auto rgbMax = 255;
+ return { static_cast<int>(r * rgbMax), static_cast<int>(g * rgbMax),
+ static_cast<int>(b * rgbMax) };
+ }
+};
+
+const QDBusArgument &operator>>(const QDBusArgument &argument, XDGDesktopColor &myStruct)
+{
+ argument.beginStructure();
+ argument >> myStruct.r >> myStruct.g >> myStruct.b;
+ argument.endStructure();
+ return argument;
+}
+
+class XdgDesktopPortalColorPicker : public QPlatformServiceColorPicker
+{
+ Q_OBJECT
+public:
+ XdgDesktopPortalColorPicker(const QString &parentWindowId, QWindow *parent)
+ : QPlatformServiceColorPicker(parent), m_parentWindowId(parentWindowId)
+ {
+ }
+
+ void pickColor() override
+ {
+ // DBus signature:
+ // PickColor (IN s parent_window,
+ // IN a{sv} options
+ // OUT o handle)
+ // Options:
+ // handle_token (s) - A string that will be used as the last element of the @handle.
+
+ QDBusMessage message = QDBusMessage::createMethodCall(
+ "org.freedesktop.portal.Desktop"_L1, "/org/freedesktop/portal/desktop"_L1,
+ "org.freedesktop.portal.Screenshot"_L1, "PickColor"_L1);
+ message << m_parentWindowId << QVariantMap();
+
+ QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
+ auto watcher = new QDBusPendingCallWatcher(pendingCall, this);
+ connect(watcher, &QDBusPendingCallWatcher::finished, this,
+ [this](QDBusPendingCallWatcher *watcher) {
+ watcher->deleteLater();
+ QDBusPendingReply<QDBusObjectPath> reply = *watcher;
+ if (reply.isError()) {
+ qWarning("DBus call to pick color failed: %s",
+ qPrintable(reply.error().message()));
+ Q_EMIT colorPicked({});
+ } else {
+ QDBusConnection::sessionBus().connect(
+ "org.freedesktop.portal.Desktop"_L1, reply.value().path(),
+ "org.freedesktop.portal.Request"_L1, "Response"_L1, this,
+ // clang-format off
+ SLOT(gotColorResponse(uint,QVariantMap))
+ // clang-format on
+ );
+ }
+ });
+ }
+
+private Q_SLOTS:
+ void gotColorResponse(uint result, const QVariantMap &map)
+ {
+ if (result != 0)
+ return;
+ 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();
+ }
+
+private:
+ const QString m_parentWindowId;
+};
+} // namespace
+
#endif // QT_CONFIG(dbus)
+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);
+ message << "org.freedesktop.portal.Screenshot"_L1
+ << "version"_L1;
+
+ QDBusPendingCall pendingCall = QDBusConnection::sessionBus().asyncCall(message);
+ auto watcher = new QDBusPendingCallWatcher(pendingCall);
+ QObject::connect(watcher, &QDBusPendingCallWatcher::finished, watcher,
+ [this](QDBusPendingCallWatcher *watcher) {
+ watcher->deleteLater();
+ QDBusPendingReply<QVariant> reply = *watcher;
+ if (!reply.isError() && reply.value().toUInt() >= 2)
+ m_hasScreenshotPortalWithColorPicking = true;
+ });
+
+#endif
+}
+
+QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
+{
+#if QT_CONFIG(dbus)
+ // Make double sure that we are in a wayland environment. In particular check
+ // WAYLAND_DISPLAY so also XWayland apps benefit from portal-based color picking.
+ // Outside wayland we'll rather rely on other means than the XDG desktop portal.
+ if (!qEnvironmentVariableIsEmpty("WAYLAND_DISPLAY")
+ || QGuiApplication::platformName().startsWith("wayland"_L1)) {
+ return new XdgDesktopPortalColorPicker(portalWindowIdentifier(parent), parent);
+ }
+ return nullptr;
+#else
+ Q_UNUSED(parent);
+ return nullptr;
+#endif
+}
+
QByteArray QGenericUnixServices::desktopEnvironment() const
{
static const QByteArray result = detectDesktopEnvironment();
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 (m_documentLauncher.isEmpty() && !detectWebBrowser(desktopEnvironment(), false, &m_documentLauncher)) {
- qWarning("Unable to detect a launcher for '%s'", qPrintable(url.toString()));
- return false;
+ if (QGuiApplication::platformName().startsWith("wayland"_L1)) {
+ runWithXdgActivationToken([openDocumentInternal, url](const QString &token) {
+ openDocumentInternal(url, token);
+ });
+
+ return true;
+ } else {
+ return openDocumentInternal(url, QString());
}
- return launch(m_documentLauncher, url);
}
#else
+QGenericUnixServices::QGenericUnixServices() = default;
+
QByteArray QGenericUnixServices::desktopEnvironment() const
{
return QByteArrayLiteral("UNKNOWN");
@@ -341,14 +554,60 @@ bool QGenericUnixServices::openDocument(const QUrl &url)
return false;
}
+QPlatformServiceColorPicker *QGenericUnixServices::colorPicker(QWindow *parent)
+{
+ Q_UNUSED(parent);
+ return nullptr;
+}
+
#endif // QT_NO_MULTIPROCESS
QString QGenericUnixServices::portalWindowIdentifier(QWindow *window)
{
- if (QGuiApplication::platformName() == QLatin1String("xcb"))
- return "x11:"_L1 + QString::number(window->winId(), 16);
-
+ Q_UNUSED(window);
return QString();
}
+bool QGenericUnixServices::hasCapability(Capability capability) const
+{
+ switch (capability) {
+ case Capability::ColorPicking:
+ return m_hasScreenshotPortalWithColorPicking;
+ }
+ 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 2ddba91a3a..56e15103f7 100644
--- a/src/gui/platform/unix/qgenericunixservices_p.h
+++ b/src/gui/platform/unix/qgenericunixservices_p.h
@@ -26,18 +26,22 @@ class QWindow;
class Q_GUI_EXPORT QGenericUnixServices : public QPlatformServices
{
public:
- QGenericUnixServices() {}
+ QGenericUnixServices();
QByteArray desktopEnvironment() const override;
+ bool hasCapability(Capability capability) const override;
bool openUrl(const QUrl &url) override;
bool openDocument(const QUrl &url) override;
+ QPlatformServiceColorPicker *colorPicker(QWindow *parent = nullptr) override;
+ void setApplicationBadge(qint64 number);
virtual QString portalWindowIdentifier(QWindow *window);
private:
QString m_webBrowser;
QString m_documentLauncher;
+ bool m_hasScreenshotPortalWithColorPicking = false;
};
QT_END_NAMESPACE
diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp
index d90d5de098..fc4b2296d2 100644
--- a/src/gui/platform/unix/qgenericunixthemes.cpp
+++ b/src/gui/platform/unix/qgenericunixthemes.cpp
@@ -1,4 +1,4 @@
-// Copyright (C) 2016 The Qt Company Ltd.
+// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR LGPL-3.0-only OR GPL-2.0-only OR GPL-3.0-only
#include "qgenericunixthemes_p.h"
@@ -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>
@@ -41,6 +47,9 @@
#include <algorithm>
QT_BEGIN_NAMESPACE
+#ifndef QT_NO_DBUS
+Q_LOGGING_CATEGORY(lcQpaThemeDBus, "qt.qpa.theme.dbus")
+#endif
using namespace Qt::StringLiterals;
@@ -74,7 +83,7 @@ static bool isDBusTrayAvailable() {
static bool dbusTrayAvailableKnown = false;
if (!dbusTrayAvailableKnown) {
QDBusMenuConnection conn;
- if (conn.isStatusNotifierHostRegistered())
+ if (conn.isWatcherRegistered())
dbusTrayAvailable = true;
dbusTrayAvailableKnown = true;
qCDebug(qLcTray) << "D-Bus tray available:" << dbusTrayAvailable;
@@ -83,6 +92,20 @@ static bool isDBusTrayAvailable() {
}
#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()
{
@@ -98,7 +121,288 @@ static bool isDBusGlobalMenuAvailable()
static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable();
return dbusGlobalMenuAvailable;
}
+
+/*!
+ * \internal
+ * The QGenericUnixThemeDBusListener class listens to the SettingChanged DBus signal
+ * 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.
+ * It is emitted on known setting types only.
+ */
+
+class QGenericUnixThemeDBusListener : public QObject
+{
+ Q_OBJECT
+
+public:
+
+ enum class Provider {
+ Kde,
+ Gtk,
+ Gnome,
+ };
+ Q_ENUM(Provider)
+
+ enum class Setting {
+ Theme,
+ ApplicationStyle,
+ ColorScheme,
+ };
+ Q_ENUM(Setting)
+
+ 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::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)));
+ }
+
+ if (dBusSignalConnected) {
+ // Connection successful
+ qCDebug(lcQpaThemeDBus) << LOG;
+ } else {
+ if (dBusRunning) {
+ // DBus running, but connection failed
+ qCWarning(lcQpaThemeDBus) << "DBus connection failed:" << LOG;
+ } else {
+ // DBus not running
+ qCWarning(lcQpaThemeDBus) << "Session DBus not running.";
+ }
+ qCWarning(lcQpaThemeDBus) << "Application will not react to setting changes.\n"
+ << "Check your DBus installation.";
+ }
+#undef LOG
+}
+
+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)
+{
+ 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
class QGenericUnixThemePrivate : public QPlatformThemePrivate
{
@@ -198,6 +502,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;
}
@@ -231,11 +539,9 @@ static QIcon xdgFileIcon(const QFileInfo &fileInfo)
#if QT_CONFIG(settings)
class QKdeThemePrivate : public QPlatformThemePrivate
{
+
public:
- QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
- : kdeDirs(kdeDirs)
- , kdeVersion(kdeVersion)
- { }
+ QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion);
static QString kdeGlobals(const QString &kdeDir, int kdeVersion)
{
@@ -266,8 +572,66 @@ public:
int startDragDist = 10;
int startDragTime = 500;
int cursorBlinkRate = 1000;
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+ void updateColorScheme(const QString &themeName);
+
+#ifndef QT_NO_DBUS
+private:
+ std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
+ bool initDbus();
+ void settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value);
+#endif // QT_NO_DBUS
};
+#ifndef QT_NO_DBUS
+void QKdeThemePrivate::settingChangedHandler(QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value)
+{
+ 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::Setting::ApplicationStyle:
+ qCDebug(lcQpaThemeDBus) << "KDE application style changed to:" << value;
+ break;
+ }
+
+ refresh();
+}
+
+bool QKdeThemePrivate::initDbus()
+{
+ dbus.reset(new QGenericUnixThemeDBusListener());
+ Q_ASSERT(dbus);
+
+ // Wrap slot in a lambda to avoid inheriting QKdeThemePrivate from QObject
+ auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value) {
+ settingChangedHandler(provider, setting, value);
+ };
+
+ return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
+}
+#endif // QT_NO_DBUS
+
+QKdeThemePrivate::QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion)
+ : kdeDirs(kdeDirs), kdeVersion(kdeVersion)
+{
+#ifndef QT_NO_DBUS
+ initDbus();
+#endif // QT_NO_DBUS
+}
+
void QKdeThemePrivate::refresh()
{
resources.clear();
@@ -297,6 +661,14 @@ void QKdeThemePrivate::refresh()
styleNames.push_front(style);
}
+ const QVariant colorScheme = readKdeSetting(QStringLiteral("ColorScheme"), kdeDirs,
+ kdeVersion, kdeSettings);
+
+ if (colorScheme.isValid())
+ updateColorScheme(colorScheme.toString());
+ else
+ m_colorScheme = Qt::ColorScheme::Unknown;
+
const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings);
if (singleClickValue.isValid())
singleClick = singleClickValue.toBool();
@@ -368,6 +740,8 @@ void QKdeThemePrivate::refresh()
if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings)))
resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont;
+ QWindowSystemInterface::handleThemeChange();
+
qCDebug(lcQpaFonts) << "default fonts: system" << resources.fonts[QPlatformTheme::SystemFont]
<< "fixed" << resources.fonts[QPlatformTheme::FixedFont];
qDeleteAll(kdeSettings);
@@ -556,6 +930,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;
}
@@ -572,6 +950,48 @@ QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions
#endif
}
+Qt::ColorScheme QKdeTheme::colorScheme() const
+{
+ return d_func()->m_colorScheme;
+}
+
+/*!
+ \internal
+ \brief QKdeTheme::updateColorScheme - 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.
+ 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
+ of the system palette.
+ */
+void QKdeThemePrivate::updateColorScheme(const QString &themeName)
+{
+ if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ return;
+ }
+ if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ return;
+ }
+
+ if (systemPalette) {
+ if (systemPalette->text().color().lightness() < systemPalette->base().color().lightness()) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ return;
+ }
+ if (systemPalette->text().color().lightness() > systemPalette->base().color().lightness()) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ return;
+ }
+ }
+
+ m_colorScheme = Qt::ColorScheme::Unknown;
+}
+
+
const QPalette *QKdeTheme::palette(Palette type) const
{
Q_D(const QKdeTheme);
@@ -672,8 +1092,8 @@ const char *QGnomeTheme::name = "gnome";
class QGnomeThemePrivate : public QPlatformThemePrivate
{
public:
- QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {}
- ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; }
+ QGnomeThemePrivate();
+ ~QGnomeThemePrivate();
void configureFonts(const QString &gtkFontName) const
{
@@ -688,10 +1108,70 @@ public:
qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont;
}
- mutable QFont *systemFont;
- mutable QFont *fixedFont;
+ mutable QFont *systemFont = nullptr;
+ mutable QFont *fixedFont = nullptr;
+
+#ifndef QT_NO_DBUS
+ Qt::ColorScheme m_colorScheme = Qt::ColorScheme::Unknown;
+private:
+ std::unique_ptr<QGenericUnixThemeDBusListener> dbus;
+ bool initDbus();
+ void updateColorScheme(const QString &themeName);
+#endif // QT_NO_DBUS
};
+QGnomeThemePrivate::QGnomeThemePrivate()
+{
+#ifndef QT_NO_DBUS
+ initDbus();
+#endif // QT_NO_DBUS
+}
+QGnomeThemePrivate::~QGnomeThemePrivate()
+{
+ if (systemFont)
+ delete systemFont;
+ if (fixedFont)
+ delete fixedFont;
+}
+
+#ifndef QT_NO_DBUS
+bool QGnomeThemePrivate::initDbus()
+{
+ dbus.reset(new QGenericUnixThemeDBusListener());
+ Q_ASSERT(dbus);
+
+ // Wrap slot in a lambda to avoid inheriting QGnomeThemePrivate from QObject
+ auto wrapper = [this](QGenericUnixThemeDBusListener::Provider provider,
+ QGenericUnixThemeDBusListener::Setting setting,
+ const QString &value) {
+ if (provider != QGenericUnixThemeDBusListener::Provider::Gnome
+ && provider != QGenericUnixThemeDBusListener::Provider::Gtk) {
+ return;
+ }
+
+ if (setting == QGenericUnixThemeDBusListener::Setting::Theme)
+ updateColorScheme(value);
+ };
+
+ return QObject::connect(dbus.get(), &QGenericUnixThemeDBusListener::settingChanged, dbus.get(), wrapper);
+}
+
+void QGnomeThemePrivate::updateColorScheme(const QString &themeName)
+{
+ const auto oldColorScheme = m_colorScheme;
+ if (themeName.contains(QLatin1StringView("light"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Light;
+ } else if (themeName.contains(QLatin1StringView("dark"), Qt::CaseInsensitive)) {
+ m_colorScheme = Qt::ColorScheme::Dark;
+ } else {
+ m_colorScheme = Qt::ColorScheme::Unknown;
+ }
+
+ if (oldColorScheme != m_colorScheme)
+ QWindowSystemInterface::handleThemeChange();
+}
+#endif // QT_NO_DBUS
+
QGnomeTheme::QGnomeTheme()
: QPlatformTheme(new QGnomeThemePrivate())
{
@@ -726,6 +1206,12 @@ QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const
case QPlatformTheme::ButtonPressKeys:
return QVariant::fromValue(
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;
}
@@ -769,6 +1255,12 @@ QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const
return new QDBusMenuBar();
return nullptr;
}
+
+Qt::ColorScheme QGnomeTheme::colorScheme() const
+{
+ return d_func()->m_colorScheme;
+}
+
#endif
#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON)
@@ -853,3 +1345,7 @@ QStringList QGenericUnixTheme::themeNames()
}
QT_END_NAMESPACE
+
+#ifndef QT_NO_DBUS
+#include "qgenericunixthemes.moc"
+#endif // QT_NO_DBUS
diff --git a/src/gui/platform/unix/qgenericunixthemes_p.h b/src/gui/platform/unix/qgenericunixthemes_p.h
index f5f60865aa..63b20651e6 100644
--- a/src/gui/platform/unix/qgenericunixthemes_p.h
+++ b/src/gui/platform/unix/qgenericunixthemes_p.h
@@ -77,6 +77,7 @@ public:
QPlatformTheme::IconOptions iconOptions = { }) const override;
const QPalette *palette(Palette type = SystemPalette) const override;
+ Qt::ColorScheme colorScheme() const override;
const QFont *font(Font type) const override;
#ifndef QT_NO_DBUS
@@ -106,6 +107,7 @@ public:
virtual QString gtkFontName() const;
#ifndef QT_NO_DBUS
QPlatformMenuBar *createPlatformMenuBar() 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 4ea88a590b..09561d9ada 100644
--- a/src/gui/platform/unix/qunixnativeinterface.cpp
+++ b/src/gui/platform/unix/qunixnativeinterface.cpp
@@ -3,7 +3,9 @@
#include <QtGui/private/qtguiglobal_p.h>
-#include <QtGui/private/qopenglcontext_p.h>
+#if QT_CONFIG(opengl)
+# include <QtGui/private/qopenglcontext_p.h>
+#endif
#include <QtGui/private/qguiapplication_p.h>
#include <qpa/qplatformopenglcontext.h>
@@ -121,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);
@@ -213,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);