From c068b80727946328711c1385681b4a32ce5f0544 Mon Sep 17 00:00:00 2001 From: Friedemann Kleint Date: Wed, 17 Jun 2020 13:59:36 +0200 Subject: Move UNIX themes into QtGui Task-number: QTBUG-83255 Change-Id: I9e3aecd8e172b60121f472c840eaf2a5538af438 Reviewed-by: Liang Qi --- src/gui/platform/unix/dbusmenu/dbusmenu.pri | 15 + .../platform/unix/dbusmenu/qdbusmenuadaptor.cpp | 163 ++++ .../platform/unix/dbusmenu/qdbusmenuadaptor_p.h | 182 +++++ src/gui/platform/unix/dbusmenu/qdbusmenubar.cpp | 179 +++++ src/gui/platform/unix/dbusmenu/qdbusmenubar_p.h | 92 +++ .../platform/unix/dbusmenu/qdbusmenuconnection.cpp | 147 ++++ .../platform/unix/dbusmenu/qdbusmenuconnection_p.h | 101 +++ .../unix/dbusmenu/qdbusmenuregistrarproxy.cpp | 64 ++ .../unix/dbusmenu/qdbusmenuregistrarproxy_p.h | 119 +++ src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp | 303 +++++++ src/gui/platform/unix/dbusmenu/qdbusmenutypes_p.h | 155 ++++ .../platform/unix/dbusmenu/qdbusplatformmenu.cpp | 308 +++++++ .../platform/unix/dbusmenu/qdbusplatformmenu_p.h | 191 +++++ src/gui/platform/unix/dbustray/dbustray.pri | 11 + src/gui/platform/unix/dbustray/qdbustrayicon.cpp | 356 +++++++++ src/gui/platform/unix/dbustray/qdbustrayicon_p.h | 168 ++++ src/gui/platform/unix/dbustray/qdbustraytypes.cpp | 212 +++++ src/gui/platform/unix/dbustray/qdbustraytypes_p.h | 109 +++ .../unix/dbustray/qstatusnotifieritemadaptor.cpp | 189 +++++ .../unix/dbustray/qstatusnotifieritemadaptor_p.h | 206 +++++ .../unix/dbustray/qxdgnotificationproxy.cpp | 53 ++ .../unix/dbustray/qxdgnotificationproxy_p.h | 143 ++++ src/gui/platform/unix/qgenericunixthemes.cpp | 884 +++++++++++++++++++++ src/gui/platform/unix/qgenericunixthemes_p.h | 156 ++++ src/gui/platform/unix/unix.pri | 12 + 25 files changed, 4518 insertions(+) create mode 100644 src/gui/platform/unix/dbusmenu/dbusmenu.pri create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenuadaptor.cpp create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenuadaptor_p.h create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenubar.cpp create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenubar_p.h create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy.cpp create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp create mode 100644 src/gui/platform/unix/dbusmenu/qdbusmenutypes_p.h create mode 100644 src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp create mode 100644 src/gui/platform/unix/dbusmenu/qdbusplatformmenu_p.h create mode 100644 src/gui/platform/unix/dbustray/dbustray.pri create mode 100644 src/gui/platform/unix/dbustray/qdbustrayicon.cpp create mode 100644 src/gui/platform/unix/dbustray/qdbustrayicon_p.h create mode 100644 src/gui/platform/unix/dbustray/qdbustraytypes.cpp create mode 100644 src/gui/platform/unix/dbustray/qdbustraytypes_p.h create mode 100644 src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp create mode 100644 src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h create mode 100644 src/gui/platform/unix/dbustray/qxdgnotificationproxy.cpp create mode 100644 src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h create mode 100644 src/gui/platform/unix/qgenericunixthemes.cpp create mode 100644 src/gui/platform/unix/qgenericunixthemes_p.h (limited to 'src/gui/platform') diff --git a/src/gui/platform/unix/dbusmenu/dbusmenu.pri b/src/gui/platform/unix/dbusmenu/dbusmenu.pri new file mode 100644 index 0000000000..c328f23144 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/dbusmenu.pri @@ -0,0 +1,15 @@ +HEADERS += \ + platform/unix/dbusmenu/qdbusmenuadaptor_p.h \ + platform/unix/dbusmenu/qdbusmenutypes_p.h \ + platform/unix/dbusmenu/qdbusmenuconnection_p.h \ + platform/unix/dbusmenu/qdbusmenubar_p.h \ + platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h \ + platform/unix/dbusmenu/qdbusplatformmenu_p.h + +SOURCES += \ + platform/unix/dbusmenu/qdbusmenuadaptor.cpp \ + platform/unix/dbusmenu/qdbusmenutypes.cpp \ + platform/unix/dbusmenu/qdbusmenuconnection.cpp \ + platform/unix/dbusmenu/qdbusmenubar.cpp \ + platform/unix/dbusmenu/qdbusmenuregistrarproxy.cpp \ + platform/unix/dbusmenu/qdbusplatformmenu.cpp diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuadaptor.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenuadaptor.cpp new file mode 100644 index 0000000000..eabb4b4122 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuadaptor.cpp @@ -0,0 +1,163 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + This file was originally created by qdbusxml2cpp version 0.8 + Command line was: + qdbusxml2cpp -a dbusmenu ../../3rdparty/dbus-ifaces/dbus-menu.xml + + However it is maintained manually. +*/ + +#include "qdbusmenuadaptor_p.h" +#include "qdbusplatformmenu_p.h" +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +QDBusMenuAdaptor::QDBusMenuAdaptor(QDBusPlatformMenu *topLevelMenu) + : QDBusAbstractAdaptor(topLevelMenu) + , m_topLevelMenu(topLevelMenu) +{ + setAutoRelaySignals(true); +} + +QDBusMenuAdaptor::~QDBusMenuAdaptor() +{ +} + +QString QDBusMenuAdaptor::status() const +{ + qCDebug(qLcMenu); + return QLatin1String("normal"); +} + +QString QDBusMenuAdaptor::textDirection() const +{ + return QLocale().textDirection() == Qt::RightToLeft ? QLatin1String("rtl") : QLatin1String("ltr"); +} + +uint QDBusMenuAdaptor::version() const +{ + return 4; +} + +bool QDBusMenuAdaptor::AboutToShow(int id) +{ + qCDebug(qLcMenu) << id; + if (id == 0) { + emit m_topLevelMenu->aboutToShow(); + } else { + QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id); + if (item) { + const QDBusPlatformMenu *menu = static_cast(item->menu()); + if (menu) + emit const_cast(menu)->aboutToShow(); + } + } + return false; // updateNeeded (we don't know that, so false) +} + +QList QDBusMenuAdaptor::AboutToShowGroup(const QList &ids, QList &idErrors) +{ + qCDebug(qLcMenu) << ids; + Q_UNUSED(idErrors) + idErrors.clear(); + for (int id : ids) + AboutToShow(id); + return QList(); // updatesNeeded +} + +void QDBusMenuAdaptor::Event(int id, const QString &eventId, const QDBusVariant &data, uint timestamp) +{ + Q_UNUSED(data) + Q_UNUSED(timestamp) + QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id); + qCDebug(qLcMenu) << id << (item ? item->text() : QLatin1String("")) << eventId; + if (item && eventId == QLatin1String("clicked")) + item->trigger(); + if (item && eventId == QLatin1String("hovered")) + emit item->hovered(); + if (eventId == QLatin1String("closed")) { + // There is no explicit AboutToHide method, so map closed event to aboutToHide method + const QDBusPlatformMenu *menu = nullptr; + if (item) + menu = static_cast(item->menu()); + else if (id == 0) + menu = m_topLevelMenu; + if (menu) + emit const_cast(menu)->aboutToHide(); + } +} + +QList QDBusMenuAdaptor::EventGroup(const QDBusMenuEventList &events) +{ + for (const QDBusMenuEvent &ev : events) + Event(ev.m_id, ev.m_eventId, ev.m_data, ev.m_timestamp); + return QList(); // idErrors +} + +QDBusMenuItemList QDBusMenuAdaptor::GetGroupProperties(const QList &ids, const QStringList &propertyNames) +{ + qCDebug(qLcMenu) << ids << propertyNames << "=>" << QDBusMenuItem::items(ids, propertyNames); + return QDBusMenuItem::items(ids, propertyNames); +} + +uint QDBusMenuAdaptor::GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, QDBusMenuLayoutItem &layout) +{ + uint ret = layout.populate(parentId, recursionDepth, propertyNames, m_topLevelMenu); + qCDebug(qLcMenu) << parentId << "depth" << recursionDepth << propertyNames << layout.m_id << layout.m_properties << "revision" << ret << layout; + return ret; +} + +QDBusVariant QDBusMenuAdaptor::GetProperty(int id, const QString &name) +{ + qCDebug(qLcMenu) << id << name; + // handle method call com.canonical.dbusmenu.GetProperty + QDBusVariant value; + return value; +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuadaptor_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuadaptor_p.h new file mode 100644 index 0000000000..6612f019a7 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuadaptor_p.h @@ -0,0 +1,182 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtDBus module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + This file was originally created by qdbusxml2cpp version 0.8 + Command line was: + qdbusxml2cpp -a dbusmenu ../../3rdparty/dbus-ifaces/dbus-menu.xml + + However it is maintained manually. + + It is also not part of the public API. This header file may change from + version to version without notice, or even be removed. +*/ + +#ifndef DBUSMENUADAPTOR_H +#define DBUSMENUADAPTOR_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include "qdbusmenutypes_p.h" + +QT_BEGIN_NAMESPACE + +/* + * Adaptor class for interface com.canonical.dbusmenu + */ +class QDBusMenuAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "com.canonical.dbusmenu") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + QDBusMenuAdaptor(QDBusPlatformMenu *topLevelMenu); + virtual ~QDBusMenuAdaptor(); + +public: // PROPERTIES + Q_PROPERTY(QString Status READ status) + QString status() const; + + Q_PROPERTY(QString TextDirection READ textDirection) + QString textDirection() const; + + Q_PROPERTY(uint Version READ version) + uint version() const; + +public Q_SLOTS: // METHODS + bool AboutToShow(int id); + QList AboutToShowGroup(const QList &ids, QList &idErrors); + void Event(int id, const QString &eventId, const QDBusVariant &data, uint timestamp); + QList EventGroup(const QDBusMenuEventList &events); + QDBusMenuItemList GetGroupProperties(const QList &ids, const QStringList &propertyNames); + uint GetLayout(int parentId, int recursionDepth, const QStringList &propertyNames, QDBusMenuLayoutItem &layout); + QDBusVariant GetProperty(int id, const QString &name); + +Q_SIGNALS: // SIGNALS + void ItemActivationRequested(int id, uint timestamp); + void ItemsPropertiesUpdated(const QDBusMenuItemList &updatedProps, const QDBusMenuItemKeysList &removedProps); + void LayoutUpdated(uint revision, int parent); + +private: + QDBusPlatformMenu *m_topLevelMenu; +}; + +QT_END_NAMESPACE + +#endif // DBUSMENUADAPTOR_H diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenubar.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenubar.cpp new file mode 100644 index 0000000000..b13c875854 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenubar.cpp @@ -0,0 +1,179 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Dmitry Shachnev +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbusmenubar_p.h" +#include "qdbusmenuregistrarproxy_p.h" + +QT_BEGIN_NAMESPACE + +/* note: do not change these to QStringLiteral; + we are unloaded before QtDBus is done using the strings. + */ +#define REGISTRAR_SERVICE QLatin1String("com.canonical.AppMenu.Registrar") +#define REGISTRAR_PATH QLatin1String("/com/canonical/AppMenu/Registrar") + +QDBusMenuBar::QDBusMenuBar() + : QPlatformMenuBar() + , m_menu(new QDBusPlatformMenu()) + , m_menuAdaptor(new QDBusMenuAdaptor(m_menu)) + , m_windowId(0) +{ + QDBusMenuItem::registerDBusTypes(); + connect(m_menu, &QDBusPlatformMenu::propertiesUpdated, + m_menuAdaptor, &QDBusMenuAdaptor::ItemsPropertiesUpdated); + connect(m_menu, &QDBusPlatformMenu::updated, + m_menuAdaptor, &QDBusMenuAdaptor::LayoutUpdated); + connect(m_menu, &QDBusPlatformMenu::popupRequested, + m_menuAdaptor, &QDBusMenuAdaptor::ItemActivationRequested); +} + +QDBusMenuBar::~QDBusMenuBar() +{ + unregisterMenuBar(); + delete m_menuAdaptor; + delete m_menu; + qDeleteAll(m_menuItems); +} + +QDBusPlatformMenuItem *QDBusMenuBar::menuItemForMenu(QPlatformMenu *menu) +{ + if (!menu) + return nullptr; + quintptr tag = menu->tag(); + const auto it = m_menuItems.constFind(tag); + if (it != m_menuItems.cend()) { + return *it; + } else { + QDBusPlatformMenuItem *item = new QDBusPlatformMenuItem; + updateMenuItem(item, menu); + m_menuItems.insert(tag, item); + return item; + } +} + +void QDBusMenuBar::updateMenuItem(QDBusPlatformMenuItem *item, QPlatformMenu *menu) +{ + const QDBusPlatformMenu *ourMenu = qobject_cast(menu); + item->setText(ourMenu->text()); + item->setIcon(ourMenu->icon()); + item->setEnabled(ourMenu->isEnabled()); + item->setVisible(ourMenu->isVisible()); + item->setMenu(menu); +} + +void QDBusMenuBar::insertMenu(QPlatformMenu *menu, QPlatformMenu *before) +{ + QDBusPlatformMenuItem *menuItem = menuItemForMenu(menu); + QDBusPlatformMenuItem *beforeItem = menuItemForMenu(before); + m_menu->insertMenuItem(menuItem, beforeItem); + m_menu->emitUpdated(); +} + +void QDBusMenuBar::removeMenu(QPlatformMenu *menu) +{ + QDBusPlatformMenuItem *menuItem = menuItemForMenu(menu); + m_menu->removeMenuItem(menuItem); + m_menu->emitUpdated(); +} + +void QDBusMenuBar::syncMenu(QPlatformMenu *menu) +{ + QDBusPlatformMenuItem *menuItem = menuItemForMenu(menu); + updateMenuItem(menuItem, menu); +} + +void QDBusMenuBar::handleReparent(QWindow *newParentWindow) +{ + if (newParentWindow) { + unregisterMenuBar(); + m_windowId = newParentWindow->winId(); + registerMenuBar(); + } +} + +QPlatformMenu *QDBusMenuBar::menuForTag(quintptr tag) const +{ + QDBusPlatformMenuItem *menuItem = m_menuItems.value(tag); + if (menuItem) + return const_cast(menuItem->menu()); + return nullptr; +} + +QPlatformMenu *QDBusMenuBar::createMenu() const +{ + return new QDBusPlatformMenu; +} + +void QDBusMenuBar::registerMenuBar() +{ + static uint menuBarId = 0; + + QDBusConnection connection = QDBusConnection::sessionBus(); + m_objectPath = QStringLiteral("/MenuBar/%1").arg(++menuBarId); + if (!connection.registerObject(m_objectPath, m_menu)) + return; + + QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, this); + QDBusPendingReply<> r = registrar.RegisterWindow(m_windowId, QDBusObjectPath(m_objectPath)); + r.waitForFinished(); + if (r.isError()) { + qWarning("Failed to register window menu, reason: %s (\"%s\")", + qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message())); + connection.unregisterObject(m_objectPath); + } +} + +void QDBusMenuBar::unregisterMenuBar() +{ + QDBusConnection connection = QDBusConnection::sessionBus(); + + if (m_windowId) { + QDBusMenuRegistrarInterface registrar(REGISTRAR_SERVICE, REGISTRAR_PATH, connection, this); + QDBusPendingReply<> r = registrar.UnregisterWindow(m_windowId); + r.waitForFinished(); + if (r.isError()) + qWarning("Failed to unregister window menu, reason: %s (\"%s\")", + qUtf8Printable(r.error().name()), qUtf8Printable(r.error().message())); + } + + if (!m_objectPath.isEmpty()) + connection.unregisterObject(m_objectPath); +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenubar_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenubar_p.h new file mode 100644 index 0000000000..364e7da4b6 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenubar_p.h @@ -0,0 +1,92 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Dmitry Shachnev +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDBUSMENUBAR_P_H +#define QDBUSMENUBAR_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDBusMenuBar : public QPlatformMenuBar +{ + Q_OBJECT + +public: + QDBusMenuBar(); + virtual ~QDBusMenuBar(); + + void insertMenu(QPlatformMenu *menu, QPlatformMenu *before) override; + void removeMenu(QPlatformMenu *menu) override; + void syncMenu(QPlatformMenu *menu) override; + void handleReparent(QWindow *newParentWindow) override; + QPlatformMenu *menuForTag(quintptr tag) const override; + QPlatformMenu *createMenu() const override; + +private: + QDBusPlatformMenu *m_menu; + QDBusMenuAdaptor *m_menuAdaptor; + QHash m_menuItems; + uint m_windowId; + QString m_objectPath; + + QDBusPlatformMenuItem *menuItemForMenu(QPlatformMenu *menu); + static void updateMenuItem(QDBusPlatformMenuItem *item, QPlatformMenu *menu); + void registerMenuBar(); + void unregisterMenuBar(); +}; + +QT_END_NAMESPACE + +#endif // QDBUSMENUBAR_P_H diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp new file mode 100644 index 0000000000..429460f9e8 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection.cpp @@ -0,0 +1,147 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include + +#ifndef QT_NO_SYSTEMTRAYICON +#include "../dbustray/qdbustrayicon_p.h" +#endif +#include "qdbusmenuconnection_p.h" +#include "qdbusmenuadaptor_p.h" +#include "qdbusplatformmenu_p.h" + +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcMenu) + +const QString StatusNotifierWatcherService = QLatin1String("org.kde.StatusNotifierWatcher"); +const QString StatusNotifierWatcherPath = QLatin1String("/StatusNotifierWatcher"); +const QString StatusNotifierItemPath = QLatin1String("/StatusNotifierItem"); +const QString MenuBarPath = QLatin1String("/MenuBar"); + +/*! + \class QDBusMenuConnection + \internal + A D-Bus connection which is used for both menu and tray icon services. + Connects to the session bus and registers with the respective watcher services. +*/ +QDBusMenuConnection::QDBusMenuConnection(QObject *parent, const QString &serviceName) + : QObject(parent) + , m_connection(serviceName.isNull() ? QDBusConnection::sessionBus() + : QDBusConnection::connectToBus(QDBusConnection::SessionBus, serviceName)) + , m_dbusWatcher(new QDBusServiceWatcher(StatusNotifierWatcherService, m_connection, QDBusServiceWatcher::WatchForRegistration, this)) + , m_statusNotifierHostRegistered(false) +{ +#ifndef QT_NO_SYSTEMTRAYICON + QDBusInterface systrayHost(StatusNotifierWatcherService, StatusNotifierWatcherPath, StatusNotifierWatcherService, m_connection); + if (systrayHost.isValid() && systrayHost.property("IsStatusNotifierHostRegistered").toBool()) + m_statusNotifierHostRegistered = true; + else + qCDebug(qLcMenu) << "StatusNotifierHost is not registered"; +#endif +} + +void QDBusMenuConnection::dbusError(const QDBusError &error) +{ + qWarning() << "QDBusTrayIcon encountered a D-Bus error:" << error; +} + +#ifndef QT_NO_SYSTEMTRAYICON +bool QDBusMenuConnection::registerTrayIconMenu(QDBusTrayIcon *item) +{ + bool success = connection().registerObject(MenuBarPath, item->menu()); + if (!success) // success == false is normal, because the object may be already registered + qCDebug(qLcMenu) << "failed to register" << item->instanceId() << MenuBarPath; + return success; +} + +void QDBusMenuConnection::unregisterTrayIconMenu(QDBusTrayIcon *item) +{ + if (item->menu()) + connection().unregisterObject(MenuBarPath); +} + +bool QDBusMenuConnection::registerTrayIcon(QDBusTrayIcon *item) +{ + bool success = connection().registerService(item->instanceId()); + if (!success) { + qWarning() << "failed to register service" << item->instanceId(); + return false; + } + + success = connection().registerObject(StatusNotifierItemPath, item); + if (!success) { + unregisterTrayIcon(item); + qWarning() << "failed to register" << item->instanceId() << StatusNotifierItemPath; + return false; + } + + if (item->menu()) + registerTrayIconMenu(item); + + return registerTrayIconWithWatcher(item); +} + +bool QDBusMenuConnection::registerTrayIconWithWatcher(QDBusTrayIcon *item) +{ + QDBusMessage registerMethod = QDBusMessage::createMethodCall( + StatusNotifierWatcherService, StatusNotifierWatcherPath, StatusNotifierWatcherService, + QLatin1String("RegisterStatusNotifierItem")); + registerMethod.setArguments(QVariantList() << item->instanceId()); + return m_connection.callWithCallback(registerMethod, this, SIGNAL(trayIconRegistered()), SLOT(dbusError(QDBusError))); +} + +bool QDBusMenuConnection::unregisterTrayIcon(QDBusTrayIcon *item) +{ + unregisterTrayIconMenu(item); + connection().unregisterObject(StatusNotifierItemPath); + bool success = connection().unregisterService(item->instanceId()); + if (!success) + qWarning() << "failed to unregister service" << item->instanceId(); + return success; +} +#endif // QT_NO_SYSTEMTRAYICON + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h new file mode 100644 index 0000000000..bbdaad1e89 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuconnection_p.h @@ -0,0 +1,101 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef DBUSCONNECTION_H +#define DBUSCONNECTION_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include + +#include +Q_MOC_INCLUDE() + +QT_BEGIN_NAMESPACE + +class QDBusServiceWatcher; +#ifndef QT_NO_SYSTEMTRAYICON +class QDBusTrayIcon; +#endif // QT_NO_SYSTEMTRAYICON + +class QDBusMenuConnection : public QObject +{ + Q_OBJECT + +public: + QDBusMenuConnection(QObject *parent = nullptr, const QString &serviceName = QString()); + QDBusConnection connection() const { return m_connection; } + QDBusServiceWatcher *dbusWatcher() const { return m_dbusWatcher; } + bool isStatusNotifierHostRegistered() const { return m_statusNotifierHostRegistered; } +#ifndef QT_NO_SYSTEMTRAYICON + bool registerTrayIconMenu(QDBusTrayIcon *item); + void unregisterTrayIconMenu(QDBusTrayIcon *item); + bool registerTrayIcon(QDBusTrayIcon *item); + bool registerTrayIconWithWatcher(QDBusTrayIcon *item); + bool unregisterTrayIcon(QDBusTrayIcon *item); +#endif // QT_NO_SYSTEMTRAYICON + +Q_SIGNALS: +#ifndef QT_NO_SYSTEMTRAYICON + void trayIconRegistered(); +#endif // QT_NO_SYSTEMTRAYICON + +private Q_SLOTS: + void dbusError(const QDBusError &error); + +private: + QDBusConnection m_connection; + QDBusServiceWatcher *m_dbusWatcher; + bool m_statusNotifierHostRegistered; +}; + +QT_END_NAMESPACE + +#endif // DBUSCONNECTION_H diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy.cpp new file mode 100644 index 0000000000..c59b5a675e --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy.cpp @@ -0,0 +1,64 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Dmitry Shachnev +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + * This file was originally created by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -p qdbusmenuregistrarproxy ../../3rdparty/dbus-ifaces/com.canonical.AppMenu.Registrar.xml + * + * However it is maintained manually. + */ + +#include "qdbusmenuregistrarproxy_p.h" + +QT_BEGIN_NAMESPACE + +/* + * Implementation of interface class QDBusMenuRegistrarInterface + */ + +QDBusMenuRegistrarInterface::QDBusMenuRegistrarInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) +{ +} + +QDBusMenuRegistrarInterface::~QDBusMenuRegistrarInterface() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h new file mode 100644 index 0000000000..cffc080f87 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenuregistrarproxy_p.h @@ -0,0 +1,119 @@ +/**************************************************************************** +** +** Copyright (C) 2016 Dmitry Shachnev +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + * This file was originally created by qdbusxml2cpp version 0.8 + * Command line was: qdbusxml2cpp -p qdbusmenuregistrarproxy ../../3rdparty/dbus-ifaces/com.canonical.AppMenu.Registrar.xml + * + * However it is maintained manually. + */ + +#ifndef QDBUSMENUREGISTRARPROXY_P_H +#define QDBUSMENUREGISTRARPROXY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +/* + * Proxy class for interface com.canonical.AppMenu.Registrar + */ +class QDBusMenuRegistrarInterface : public QDBusAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { + return "com.canonical.AppMenu.Registrar"; + } + +public: + explicit QDBusMenuRegistrarInterface(const QString &service, + const QString &path, + const QDBusConnection &connection, + QObject *parent = nullptr); + + ~QDBusMenuRegistrarInterface(); + +public Q_SLOTS: // METHODS + QDBusPendingReply GetMenuForWindow(uint windowId) + { + return asyncCall(QStringLiteral("GetMenuForWindow"), windowId); + } + QDBusReply GetMenuForWindow(uint windowId, QDBusObjectPath &menuObjectPath) + { + QDBusMessage reply = call(QDBus::Block, QStringLiteral("GetMenuForWindow"), windowId); + QList arguments = reply.arguments(); + if (reply.type() == QDBusMessage::ReplyMessage && arguments.count() == 2) + menuObjectPath = qdbus_cast(arguments.at(1)); + return reply; + } + + QDBusPendingReply<> RegisterWindow(uint windowId, const QDBusObjectPath &menuObjectPath) + { + return asyncCall(QStringLiteral("RegisterWindow"), windowId, menuObjectPath); + } + + QDBusPendingReply<> UnregisterWindow(uint windowId) + { + return asyncCall(QStringLiteral("UnregisterWindow"), windowId); + } +}; + +QT_END_NAMESPACE + +#endif // QDBUSMENUREGISTRARPROXY_P_H diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp new file mode 100644 index 0000000000..6fadea5d28 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenutypes.cpp @@ -0,0 +1,303 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbusmenutypes_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(shortcut) +# include +#endif +#include +#include "qdbusplatformmenu_p.h" + +QT_BEGIN_NAMESPACE + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuItem &item) +{ + arg.beginStructure(); + arg << item.m_id << item.m_properties; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuItem &item) +{ + arg.beginStructure(); + arg >> item.m_id >> item.m_properties; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuItemKeys &keys) +{ + arg.beginStructure(); + arg << keys.id << keys.properties; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuItemKeys &keys) +{ + arg.beginStructure(); + arg >> keys.id >> keys.properties; + arg.endStructure(); + return arg; +} + +uint QDBusMenuLayoutItem::populate(int id, int depth, const QStringList &propertyNames, const QDBusPlatformMenu *topLevelMenu) +{ + qCDebug(qLcMenu) << id << "depth" << depth << propertyNames; + m_id = id; + if (id == 0) { + m_properties.insert(QLatin1String("children-display"), QLatin1String("submenu")); + if (topLevelMenu) + populate(topLevelMenu, depth, propertyNames); + return 1; // revision + } + + QDBusPlatformMenuItem *item = QDBusPlatformMenuItem::byId(id); + if (item) { + const QDBusPlatformMenu *menu = static_cast(item->menu()); + + if (menu) { + if (depth != 0) + populate(menu, depth, propertyNames); + return menu->revision(); + } + } + + return 1; // revision +} + +void QDBusMenuLayoutItem::populate(const QDBusPlatformMenu *menu, int depth, const QStringList &propertyNames) +{ + const auto items = menu->items(); + for (QDBusPlatformMenuItem *item : items) { + QDBusMenuLayoutItem child; + child.populate(item, depth - 1, propertyNames); + m_children << child; + } +} + +void QDBusMenuLayoutItem::populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames) +{ + m_id = item->dbusID(); + QDBusMenuItem proxy(item); + m_properties = proxy.m_properties; + + const QDBusPlatformMenu *menu = static_cast(item->menu()); + if (depth != 0 && menu) + populate(menu, depth, propertyNames); +} + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuLayoutItem &item) +{ + arg.beginStructure(); + arg << item.m_id << item.m_properties; + arg.beginArray(qMetaTypeId()); + for (const QDBusMenuLayoutItem &child : item.m_children) + arg << QDBusVariant(QVariant::fromValue(child)); + arg.endArray(); + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuLayoutItem &item) +{ + arg.beginStructure(); + arg >> item.m_id >> item.m_properties; + arg.beginArray(); + while (!arg.atEnd()) { + QDBusVariant dbusVariant; + arg >> dbusVariant; + QDBusArgument childArgument = qvariant_cast(dbusVariant.variant()); + + QDBusMenuLayoutItem child; + childArgument >> child; + item.m_children.append(child); + } + arg.endArray(); + arg.endStructure(); + return arg; +} + +void QDBusMenuItem::registerDBusTypes() +{ + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); +} + +QDBusMenuItem::QDBusMenuItem(const QDBusPlatformMenuItem *item) + : m_id(item->dbusID()) +{ + if (item->isSeparator()) { + m_properties.insert(QLatin1String("type"), QLatin1String("separator")); + } else { + m_properties.insert(QLatin1String("label"), convertMnemonic(item->text())); + if (item->menu()) + m_properties.insert(QLatin1String("children-display"), QLatin1String("submenu")); + m_properties.insert(QLatin1String("enabled"), item->isEnabled()); + if (item->isCheckable()) { + QString toggleType = item->hasExclusiveGroup() ? QLatin1String("radio") : QLatin1String("checkmark"); + m_properties.insert(QLatin1String("toggle-type"), toggleType); + m_properties.insert(QLatin1String("toggle-state"), item->isChecked() ? 1 : 0); + } +#ifndef QT_NO_SHORTCUT + const QKeySequence &scut = item->shortcut(); + if (!scut.isEmpty()) { + QDBusMenuShortcut shortcut = convertKeySequence(scut); + m_properties.insert(QLatin1String("shortcut"), QVariant::fromValue(shortcut)); + } +#endif + const QIcon &icon = item->icon(); + if (!icon.name().isEmpty()) { + m_properties.insert(QLatin1String("icon-name"), icon.name()); + } else if (!icon.isNull()) { + QBuffer buf; + icon.pixmap(16).save(&buf, "PNG"); + m_properties.insert(QLatin1String("icon-data"), buf.data()); + } + } + m_properties.insert(QLatin1String("visible"), item->isVisible()); +} + +QDBusMenuItemList QDBusMenuItem::items(const QList &ids, const QStringList &propertyNames) +{ + Q_UNUSED(propertyNames) + QDBusMenuItemList ret; + const QList items = QDBusPlatformMenuItem::byIds(ids); + ret.reserve(items.size()); + for (const QDBusPlatformMenuItem *item : items) + ret << QDBusMenuItem(item); + return ret; +} + +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(QLatin1Char('&')); + if (idx < 0 || idx == label.length() - 1) + return label; + QString ret(label); + ret[idx] = QLatin1Char('_'); + return ret; +} + +#ifndef QT_NO_SHORTCUT +QDBusMenuShortcut QDBusMenuItem::convertKeySequence(const QKeySequence &sequence) +{ + QDBusMenuShortcut shortcut; + for (int i = 0; i < sequence.count(); ++i) { + QStringList tokens; + int key = sequence[i]; + if (key & Qt::MetaModifier) + tokens << QStringLiteral("Super"); + if (key & Qt::ControlModifier) + tokens << QStringLiteral("Control"); + if (key & Qt::AltModifier) + tokens << QStringLiteral("Alt"); + if (key & Qt::ShiftModifier) + tokens << QStringLiteral("Shift"); + if (key & Qt::KeypadModifier) + tokens << QStringLiteral("Num"); + + QString keyName = QKeySequencePrivate::keyName(key, QKeySequence::PortableText); + if (keyName == QLatin1String("+")) + tokens << QStringLiteral("plus"); + else if (keyName == QLatin1String("-")) + tokens << QStringLiteral("minus"); + else + tokens << keyName; + shortcut << tokens; + } + return shortcut; +} +#endif + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuEvent &ev) +{ + arg.beginStructure(); + arg << ev.m_id << ev.m_eventId << ev.m_data << ev.m_timestamp; + arg.endStructure(); + return arg; +} + +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuEvent &ev) +{ + arg.beginStructure(); + arg >> ev.m_id >> ev.m_eventId >> ev.m_data >> ev.m_timestamp; + arg.endStructure(); + return arg; +} + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QDBusMenuItem &item) +{ + QDebugStateSaver saver(d); + d.nospace(); + d << "QDBusMenuItem(id=" << item.m_id << ", properties=" << item.m_properties << ')'; + return d; +} + +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)"; + return d; +} +#endif + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusmenutypes_p.h b/src/gui/platform/unix/dbusmenu/qdbusmenutypes_p.h new file mode 100644 index 0000000000..fd6727d3be --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusmenutypes_p.h @@ -0,0 +1,155 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDBUSMENUTYPES_H +#define QDBUSMENUTYPES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class QDBusPlatformMenu; +class QDBusPlatformMenuItem; +class QDBusMenuItem; +typedef QVector QDBusMenuItemList; +typedef QVector QDBusMenuShortcut; + +class QDBusMenuItem +{ +public: + QDBusMenuItem() { } + QDBusMenuItem(const QDBusPlatformMenuItem *item); + + static QDBusMenuItemList items(const QList &ids, const QStringList &propertyNames); + static QString convertMnemonic(const QString &label); +#ifndef QT_NO_SHORTCUT + static QDBusMenuShortcut convertKeySequence(const QKeySequence &sequence); +#endif + static void registerDBusTypes(); + + int m_id; + QVariantMap m_properties; +}; +Q_DECLARE_TYPEINFO(QDBusMenuItem, Q_MOVABLE_TYPE); + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuItem &item); +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuItem &item); + +class QDBusMenuItemKeys +{ +public: + + int id; + QStringList properties; +}; +Q_DECLARE_TYPEINFO(QDBusMenuItemKeys, Q_MOVABLE_TYPE); + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuItemKeys &keys); +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuItemKeys &keys); + +typedef QVector QDBusMenuItemKeysList; + +class QDBusMenuLayoutItem +{ +public: + uint populate(int id, int depth, const QStringList &propertyNames, const QDBusPlatformMenu *topLevelMenu); + void populate(const QDBusPlatformMenu *menu, int depth, const QStringList &propertyNames); + void populate(const QDBusPlatformMenuItem *item, int depth, const QStringList &propertyNames); + + int m_id; + QVariantMap m_properties; + QVector m_children; +}; +Q_DECLARE_TYPEINFO(QDBusMenuLayoutItem, Q_MOVABLE_TYPE); + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuLayoutItem &); +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuLayoutItem &item); + +typedef QVector QDBusMenuLayoutItemList; + +class QDBusMenuEvent +{ +public: + int m_id; + QString m_eventId; + QDBusVariant m_data; + uint m_timestamp; +}; +Q_DECLARE_TYPEINFO(QDBusMenuEvent, Q_MOVABLE_TYPE); // QDBusVariant is movable, even though it cannot + // be marked as such until Qt 6. + +const QDBusArgument &operator<<(QDBusArgument &arg, const QDBusMenuEvent &ev); +const QDBusArgument &operator>>(const QDBusArgument &arg, QDBusMenuEvent &ev); + +typedef QVector QDBusMenuEventList; + +#ifndef QT_NO_DEBUG_STREAM +QDebug operator<<(QDebug d, const QDBusMenuItem &item); +QDebug operator<<(QDebug d, const QDBusMenuLayoutItem &item); +#endif + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QDBusMenuItem) +Q_DECLARE_METATYPE(QDBusMenuItemList) +Q_DECLARE_METATYPE(QDBusMenuItemKeys) +Q_DECLARE_METATYPE(QDBusMenuItemKeysList) +Q_DECLARE_METATYPE(QDBusMenuLayoutItem) +Q_DECLARE_METATYPE(QDBusMenuLayoutItemList) +Q_DECLARE_METATYPE(QDBusMenuEvent) +Q_DECLARE_METATYPE(QDBusMenuEventList) +Q_DECLARE_METATYPE(QDBusMenuShortcut) + +#endif diff --git a/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp b/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp new file mode 100644 index 0000000000..fc1b37f2f2 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusplatformmenu.cpp @@ -0,0 +1,308 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbusplatformmenu_p.h" + +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcMenu, "qt.qpa.menu") + +static int nextDBusID = 1; +QHash menuItemsByID; + +QDBusPlatformMenuItem::QDBusPlatformMenuItem() + : m_subMenu(nullptr) + , m_role(NoRole) + , m_isEnabled(true) + , m_isVisible(true) + , m_isSeparator(false) + , m_isCheckable(false) + , m_isChecked(false) + , m_hasExclusiveGroup(false) + , m_dbusID(nextDBusID++) +{ + menuItemsByID.insert(m_dbusID, this); +} + +QDBusPlatformMenuItem::~QDBusPlatformMenuItem() +{ + menuItemsByID.remove(m_dbusID); + if (m_subMenu) + static_cast(m_subMenu)->setContainingMenuItem(nullptr); +} + +void QDBusPlatformMenuItem::setText(const QString &text) +{ + qCDebug(qLcMenu) << m_dbusID << text; + m_text = text; +} + +void QDBusPlatformMenuItem::setIcon(const QIcon &icon) +{ + m_icon = icon; +} + +/*! + Set a submenu under this menu item. +*/ +void QDBusPlatformMenuItem::setMenu(QPlatformMenu *menu) +{ + if (m_subMenu) + static_cast(m_subMenu)->setContainingMenuItem(nullptr); + m_subMenu = menu; + if (menu) + static_cast(menu)->setContainingMenuItem(this); +} + +void QDBusPlatformMenuItem::setEnabled(bool enabled) +{ + m_isEnabled = enabled; +} + +void QDBusPlatformMenuItem::setVisible(bool isVisible) +{ + m_isVisible = isVisible; +} + +void QDBusPlatformMenuItem::setIsSeparator(bool isSeparator) +{ + m_isSeparator = isSeparator; +} + +void QDBusPlatformMenuItem::setRole(QPlatformMenuItem::MenuRole role) +{ + m_role = role; +} + +void QDBusPlatformMenuItem::setCheckable(bool checkable) +{ + m_isCheckable = checkable; +} + +void QDBusPlatformMenuItem::setChecked(bool isChecked) +{ + m_isChecked = isChecked; +} + +void QDBusPlatformMenuItem::setHasExclusiveGroup(bool hasExclusiveGroup) +{ + m_hasExclusiveGroup = hasExclusiveGroup; +} + +#ifndef QT_NO_SHORTCUT +void QDBusPlatformMenuItem::setShortcut(const QKeySequence &shortcut) +{ + m_shortcut = shortcut; +} +#endif + +void QDBusPlatformMenuItem::trigger() +{ + emit activated(); +} + +QDBusPlatformMenuItem *QDBusPlatformMenuItem::byId(int id) +{ + // We need to check contains because otherwise QHash would insert + // a default-constructed nullptr value into menuItemsByID + if (menuItemsByID.contains(id)) + return menuItemsByID[id]; + return nullptr; +} + +QList QDBusPlatformMenuItem::byIds(const QList &ids) +{ + QList ret; + for (int id : ids) { + if (menuItemsByID.contains(id)) + ret << menuItemsByID[id]; + } + return ret; +} + + +QDBusPlatformMenu::QDBusPlatformMenu() + : m_isEnabled(true) + , m_isVisible(true) + , m_revision(1) + , m_containingMenuItem(nullptr) +{ +} + +QDBusPlatformMenu::~QDBusPlatformMenu() +{ + if (m_containingMenuItem) + m_containingMenuItem->setMenu(nullptr); +} + +void QDBusPlatformMenu::insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) +{ + QDBusPlatformMenuItem *item = static_cast(menuItem); + QDBusPlatformMenuItem *beforeItem = static_cast(before); + int idx = m_items.indexOf(beforeItem); + qCDebug(qLcMenu) << item->dbusID() << item->text(); + if (idx < 0) + m_items.append(item); + else + m_items.insert(idx, item); + m_itemsByTag.insert(item->tag(), item); + if (item->menu()) + syncSubMenu(static_cast(item->menu())); + emitUpdated(); +} + +void QDBusPlatformMenu::removeMenuItem(QPlatformMenuItem *menuItem) +{ + QDBusPlatformMenuItem *item = static_cast(menuItem); + m_items.removeAll(item); + m_itemsByTag.remove(menuItem->tag()); + if (item->menu()) { + // disconnect from the signals we connected to in syncSubMenu() + const QDBusPlatformMenu *menu = static_cast(item->menu()); + disconnect(menu, &QDBusPlatformMenu::propertiesUpdated, + this, &QDBusPlatformMenu::propertiesUpdated); + disconnect(menu, &QDBusPlatformMenu::updated, + this, &QDBusPlatformMenu::updated); + disconnect(menu, &QDBusPlatformMenu::popupRequested, + this, &QDBusPlatformMenu::popupRequested); + } + emitUpdated(); +} + +void QDBusPlatformMenu::syncSubMenu(const QDBusPlatformMenu *menu) +{ + // The adaptor is only connected to the propertiesUpdated signal of the top-level + // menu, so the submenus should transfer their signals to their parents. + connect(menu, &QDBusPlatformMenu::propertiesUpdated, + this, &QDBusPlatformMenu::propertiesUpdated, Qt::UniqueConnection); + connect(menu, &QDBusPlatformMenu::updated, + this, &QDBusPlatformMenu::updated, Qt::UniqueConnection); + connect(menu, &QDBusPlatformMenu::popupRequested, + this, &QDBusPlatformMenu::popupRequested, Qt::UniqueConnection); +} + +void QDBusPlatformMenu::syncMenuItem(QPlatformMenuItem *menuItem) +{ + QDBusPlatformMenuItem *item = static_cast(menuItem); + // if a submenu was added to this item, we need to connect to its signals + if (item->menu()) + syncSubMenu(static_cast(item->menu())); + // TODO keep around copies of the QDBusMenuLayoutItems so they can be updated? + // or eliminate them by putting dbus streaming operators in this class instead? + // or somehow tell the dbusmenu client that something has changed, so it will ask for properties again + QDBusMenuItemList updated; + QDBusMenuItemKeysList removed; + updated << QDBusMenuItem(item); + qCDebug(qLcMenu) << updated; + emit propertiesUpdated(updated, removed); +} + +void QDBusPlatformMenu::emitUpdated() +{ + if (m_containingMenuItem) + emit updated(++m_revision, m_containingMenuItem->dbusID()); + else + emit updated(++m_revision, 0); +} + +void QDBusPlatformMenu::setText(const QString &text) +{ + m_text = text; +} + +void QDBusPlatformMenu::setIcon(const QIcon &icon) +{ + m_icon = icon; +} + +void QDBusPlatformMenu::setEnabled(bool enabled) +{ + m_isEnabled = enabled; +} + +void QDBusPlatformMenu::setVisible(bool isVisible) +{ + m_isVisible = isVisible; +} + +void QDBusPlatformMenu::setContainingMenuItem(QDBusPlatformMenuItem *item) +{ + m_containingMenuItem = item; +} + +void QDBusPlatformMenu::showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) +{ + Q_UNUSED(parentWindow); + Q_UNUSED(targetRect); + Q_UNUSED(item); + setVisible(true); + emit popupRequested(m_containingMenuItem->dbusID(), QDateTime::currentMSecsSinceEpoch()); +} + +QPlatformMenuItem *QDBusPlatformMenu::menuItemAt(int position) const +{ + return m_items.value(position); +} + +QPlatformMenuItem *QDBusPlatformMenu::menuItemForTag(quintptr tag) const +{ + return m_itemsByTag[tag]; +} + +const QList QDBusPlatformMenu::items() const +{ + return m_items; +} + +QPlatformMenuItem *QDBusPlatformMenu::createMenuItem() const +{ + QDBusPlatformMenuItem *ret = new QDBusPlatformMenuItem(); + return ret; +} + +QPlatformMenu *QDBusPlatformMenu::createSubMenu() const +{ + return new QDBusPlatformMenu; +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbusmenu/qdbusplatformmenu_p.h b/src/gui/platform/unix/dbusmenu/qdbusplatformmenu_p.h new file mode 100644 index 0000000000..aa0f303416 --- /dev/null +++ b/src/gui/platform/unix/dbusmenu/qdbusplatformmenu_p.h @@ -0,0 +1,191 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDBUSPLATFORMMENU_H +#define QDBUSPLATFORMMENU_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// +// +// W A R N I N G +// ------------- +// +// This file is part of the DBus menu support and is not meant to be used +// in applications. Usage of this API may make your code +// source and binary incompatible with future versions of Qt. +// + +#include +#include +#include "qdbusmenutypes_p.h" + +QT_BEGIN_NAMESPACE +Q_DECLARE_LOGGING_CATEGORY(qLcMenu) + +class QDBusPlatformMenu; + +class QDBusPlatformMenuItem : public QPlatformMenuItem +{ + Q_OBJECT + +public: + QDBusPlatformMenuItem(); + ~QDBusPlatformMenuItem(); + + const QString text() const { return m_text; } + void setText(const QString &text) override; + QIcon icon() const { return m_icon; } + void setIcon(const QIcon &icon) override; + const QPlatformMenu *menu() const { return m_subMenu; } + void setMenu(QPlatformMenu *menu) override; + bool isEnabled() const { return m_isEnabled; } + void setEnabled(bool enabled) override; + bool isVisible() const { return m_isVisible; } + void setVisible(bool isVisible) override; + bool isSeparator() const { return m_isSeparator; } + void setIsSeparator(bool isSeparator) override; + void setFont(const QFont &font) override { Q_UNUSED(font); } + void setRole(MenuRole role) override; + bool isCheckable() const { return m_isCheckable; } + void setCheckable(bool checkable) override; + bool isChecked() const { return m_isChecked; } + void setChecked(bool isChecked) override; + bool hasExclusiveGroup() const { return m_hasExclusiveGroup; } + void setHasExclusiveGroup(bool hasExclusiveGroup) override; +#if QT_CONFIG(shortcut) + QKeySequence shortcut() const { return m_shortcut; } + void setShortcut(const QKeySequence& shortcut) override; +#endif + void setIconSize(int size) override { Q_UNUSED(size); } + void setNativeContents(WId item) override { Q_UNUSED(item); } + + int dbusID() const { return m_dbusID; } + + void trigger(); + + static QDBusPlatformMenuItem *byId(int id); + static QList byIds(const QList &ids); + +private: + QString m_text; + QIcon m_icon; + QPlatformMenu *m_subMenu; + MenuRole m_role : 4; + bool m_isEnabled : 1; + bool m_isVisible : 1; + bool m_isSeparator : 1; + bool m_isCheckable : 1; + bool m_isChecked : 1; + bool m_hasExclusiveGroup : 1; + short /*unused*/ : 6; + short m_dbusID : 16; +#if QT_CONFIG(shortcut) + QKeySequence m_shortcut; +#endif +}; + +class QDBusPlatformMenu : public QPlatformMenu +{ + Q_OBJECT + +public: + QDBusPlatformMenu(); + ~QDBusPlatformMenu(); + void insertMenuItem(QPlatformMenuItem *menuItem, QPlatformMenuItem *before) override; + void removeMenuItem(QPlatformMenuItem *menuItem) override; + void syncSubMenu(const QDBusPlatformMenu *menu); + void syncMenuItem(QPlatformMenuItem *menuItem) override; + void syncSeparatorsCollapsible(bool enable) override { Q_UNUSED(enable); } + + const QString text() const { return m_text; } + void setText(const QString &text) override; + QIcon icon() const { return m_icon; } + void setIcon(const QIcon &icon) override; + bool isEnabled() const override { return m_isEnabled; } + void setEnabled(bool enabled) override; + bool isVisible() const { return m_isVisible; } + void setVisible(bool visible) override; + void setMinimumWidth(int width) override { Q_UNUSED(width); } + void setFont(const QFont &font) override { Q_UNUSED(font); } + void setMenuType(MenuType type) override { Q_UNUSED(type); } + void setContainingMenuItem(QDBusPlatformMenuItem *item); + + void showPopup(const QWindow *parentWindow, const QRect &targetRect, const QPlatformMenuItem *item) override; + + void dismiss() override { } // Closes this and all its related menu popups + + QPlatformMenuItem *menuItemAt(int position) const override; + QPlatformMenuItem *menuItemForTag(quintptr tag) const override; + const QList items() const; + + QPlatformMenuItem *createMenuItem() const override; + QPlatformMenu *createSubMenu() const override; + + uint revision() const { return m_revision; } + + void emitUpdated(); + +signals: + void updated(uint revision, int dbusId); + void propertiesUpdated(QDBusMenuItemList updatedProps, QDBusMenuItemKeysList removedProps); + void popupRequested(int id, uint timestamp); + +private: + QString m_text; + QIcon m_icon; + bool m_isEnabled; + bool m_isVisible; + uint m_revision; + QHash m_itemsByTag; + QList m_items; + QDBusPlatformMenuItem *m_containingMenuItem; +}; + +QT_END_NAMESPACE + +#endif + diff --git a/src/gui/platform/unix/dbustray/dbustray.pri b/src/gui/platform/unix/dbustray/dbustray.pri new file mode 100644 index 0000000000..cc5b40ef42 --- /dev/null +++ b/src/gui/platform/unix/dbustray/dbustray.pri @@ -0,0 +1,11 @@ +HEADERS += \ + platform/unix/dbustray/qdbustrayicon_p.h \ + platform/unix/dbustray/qdbustraytypes_p.h \ + platform/unix/dbustray/qstatusnotifieritemadaptor_p.h \ + platform/unix/dbustray/qxdgnotificationproxy_p.h + +SOURCES += \ + platform/unix/dbustray/qdbustrayicon.cpp \ + platform/unix/dbustray/qdbustraytypes.cpp \ + platform/unix/dbustray/qstatusnotifieritemadaptor.cpp \ + platform/unix/dbustray/qxdgnotificationproxy.cpp diff --git a/src/gui/platform/unix/dbustray/qdbustrayicon.cpp b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp new file mode 100644 index 0000000000..e8fcb83c38 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qdbustrayicon.cpp @@ -0,0 +1,356 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qdbustrayicon_p.h" + +#ifndef QT_NO_SYSTEMTRAYICON + +#include +#include "qstatusnotifieritemadaptor_p.h" +#include +#include +#include "qxdgnotificationproxy_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +// Defined in Windows headers which get included by qlockfile_p.h +#undef interface + +QT_BEGIN_NAMESPACE + +Q_LOGGING_CATEGORY(qLcTray, "qt.qpa.tray") + +static QString iconTempPath() +{ + QString tempPath = QStandardPaths::writableLocation(QStandardPaths::RuntimeLocation); + if (!tempPath.isEmpty()) + return tempPath; + + tempPath = QStandardPaths::writableLocation(QStandardPaths::GenericCacheLocation); + + if (!tempPath.isEmpty()) { + QDir tempDir(tempPath); + if (tempDir.exists()) + return tempPath; + + if (tempDir.mkpath(QStringLiteral("."))) { + const QFile::Permissions permissions = QFile::ReadOwner | QFile::WriteOwner | QFile::ExeOwner; + if (QFile(tempPath).setPermissions(permissions)) + return tempPath; + } + } + + return QDir::tempPath(); +} + +static const QString KDEItemFormat = QStringLiteral("org.kde.StatusNotifierItem-%1-%2"); +static const QString KDEWatcherService = QStringLiteral("org.kde.StatusNotifierWatcher"); +static const QString XdgNotificationService = QStringLiteral("org.freedesktop.Notifications"); +static const QString XdgNotificationPath = QStringLiteral("/org/freedesktop/Notifications"); +static const QString DefaultAction = QStringLiteral("default"); +static int instanceCount = 0; + +static inline QString tempFileTemplate() +{ + static const QString TempFileTemplate = iconTempPath() + QLatin1String("/qt-trayicon-XXXXXX.png"); + return TempFileTemplate; +} + +/*! + \class QDBusTrayIcon + \internal +*/ + +QDBusTrayIcon::QDBusTrayIcon() + : m_dbusConnection(nullptr) + , m_adaptor(new QStatusNotifierItemAdaptor(this)) + , m_menuAdaptor(nullptr) + , m_menu(nullptr) + , m_notifier(nullptr) + , m_instanceId(KDEItemFormat.arg(QCoreApplication::applicationPid()).arg(++instanceCount)) + , m_category(QStringLiteral("ApplicationStatus")) + , m_defaultStatus(QStringLiteral("Active")) // be visible all the time. QSystemTrayIcon has no API to control this. + , m_status(m_defaultStatus) + , m_tempIcon(nullptr) + , m_tempAttentionIcon(nullptr) + , m_registered(false) +{ + qCDebug(qLcTray); + if (instanceCount == 1) { + QDBusMenuItem::registerDBusTypes(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + qDBusRegisterMetaType(); + } + connect(this, SIGNAL(statusChanged(QString)), m_adaptor, SIGNAL(NewStatus(QString))); + connect(this, SIGNAL(tooltipChanged()), m_adaptor, SIGNAL(NewToolTip())); + connect(this, SIGNAL(iconChanged()), m_adaptor, SIGNAL(NewIcon())); + connect(this, SIGNAL(attention()), m_adaptor, SIGNAL(NewAttentionIcon())); + connect(this, SIGNAL(menuChanged()), m_adaptor, SIGNAL(NewMenu())); + connect(this, SIGNAL(attention()), m_adaptor, SIGNAL(NewTitle())); + connect(&m_attentionTimer, SIGNAL(timeout()), this, SLOT(attentionTimerExpired())); + m_attentionTimer.setSingleShot(true); +} + +QDBusTrayIcon::~QDBusTrayIcon() +{ +} + +void QDBusTrayIcon::init() +{ + qCDebug(qLcTray) << "registering" << m_instanceId; + m_registered = dBusConnection()->registerTrayIcon(this); + QObject::connect(dBusConnection()->dbusWatcher(), &QDBusServiceWatcher::serviceRegistered, + this, &QDBusTrayIcon::watcherServiceRegistered); +} + +void QDBusTrayIcon::cleanup() +{ + qCDebug(qLcTray) << "unregistering" << m_instanceId; + if (m_registered) + dBusConnection()->unregisterTrayIcon(this); + delete m_dbusConnection; + m_dbusConnection = nullptr; + delete m_notifier; + m_notifier = nullptr; + m_registered = false; +} + +void QDBusTrayIcon::watcherServiceRegistered(const QString &serviceName) +{ + Q_UNUSED(serviceName); + // We have the icon registered, but the watcher has restarted or + // changed, so we need to tell it about our icon again + if (m_registered) + dBusConnection()->registerTrayIconWithWatcher(this); +} + +void QDBusTrayIcon::attentionTimerExpired() +{ + m_messageTitle = QString(); + m_message = QString(); + m_attentionIcon = QIcon(); + emit attention(); + emit tooltipChanged(); + setStatus(m_defaultStatus); +} + +void QDBusTrayIcon::setStatus(const QString &status) +{ + qCDebug(qLcTray) << status; + if (m_status == status) + return; + m_status = status; + emit statusChanged(m_status); +} + +QTemporaryFile *QDBusTrayIcon::tempIcon(const QIcon &icon) +{ + // Hack for indicator-application, which doesn't handle icons sent across D-Bus: + // save the icon to a temp file and set the icon name to that filename. + static bool necessity_checked = false; + static bool necessary = false; + if (!necessity_checked) { + QDBusConnection session = QDBusConnection::sessionBus(); + uint pid = session.interface()->servicePid(KDEWatcherService).value(); + QString processName = QLockFilePrivate::processNameByPid(pid); + necessary = processName.endsWith(QLatin1String("indicator-application-service")); + if (!necessary && QGuiApplication::desktopSettingsAware()) { + // Accessing to process name might be not allowed if the application + // is confined, thus we can just rely on the current desktop in use + const QPlatformServices *services = QGuiApplicationPrivate::platformIntegration()->services(); + necessary = services->desktopEnvironment().split(':').contains("UNITY"); + } + necessity_checked = true; + } + if (!necessary) + return nullptr; + qreal dpr = qGuiApp->devicePixelRatio(); + QTemporaryFile *ret = new QTemporaryFile(tempFileTemplate(), this); + ret->open(); + icon.pixmap(QSize(22 * dpr, 22 * dpr)).save(ret); + ret->close(); + return ret; +} + +QDBusMenuConnection * QDBusTrayIcon::dBusConnection() +{ + if (!m_dbusConnection) { + m_dbusConnection = new QDBusMenuConnection(this, m_instanceId); + m_notifier = new QXdgNotificationInterface(XdgNotificationService, + XdgNotificationPath, m_dbusConnection->connection(), this); + connect(m_notifier, SIGNAL(NotificationClosed(uint,uint)), this, SLOT(notificationClosed(uint,uint))); + connect(m_notifier, SIGNAL(ActionInvoked(uint,QString)), this, SLOT(actionInvoked(uint,QString))); + } + return m_dbusConnection; +} + +void QDBusTrayIcon::updateIcon(const QIcon &icon) +{ + m_iconName = icon.name(); + m_icon = icon; + if (m_iconName.isEmpty()) { + if (m_tempIcon) + delete m_tempIcon; + m_tempIcon = tempIcon(icon); + if (m_tempIcon) + m_iconName = m_tempIcon->fileName(); + } + qCDebug(qLcTray) << m_iconName << icon.availableSizes(); + emit iconChanged(); +} + +void QDBusTrayIcon::updateToolTip(const QString &tooltip) +{ + qCDebug(qLcTray) << tooltip; + m_tooltip = tooltip; + emit tooltipChanged(); +} + +QPlatformMenu *QDBusTrayIcon::createMenu() const +{ + return new QDBusPlatformMenu(); +} + +void QDBusTrayIcon::updateMenu(QPlatformMenu * menu) +{ + qCDebug(qLcTray) << menu; + QDBusPlatformMenu *newMenu = qobject_cast(menu); + if (m_menu != newMenu) { + if (m_menu) { + dBusConnection()->unregisterTrayIconMenu(this); + delete m_menuAdaptor; + } + m_menu = newMenu; + m_menuAdaptor = new QDBusMenuAdaptor(m_menu); + // TODO connect(m_menu, , m_menuAdaptor, SIGNAL(ItemActivationRequested(int,uint))); + connect(m_menu, SIGNAL(propertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList)), + m_menuAdaptor, SIGNAL(ItemsPropertiesUpdated(QDBusMenuItemList,QDBusMenuItemKeysList))); + connect(m_menu, SIGNAL(updated(uint,int)), + m_menuAdaptor, SIGNAL(LayoutUpdated(uint,int))); + dBusConnection()->registerTrayIconMenu(this); + emit menuChanged(); + } +} + +void QDBusTrayIcon::showMessage(const QString &title, const QString &msg, const QIcon &icon, + QPlatformSystemTrayIcon::MessageIcon iconType, int msecs) +{ + m_messageTitle = title; + m_message = msg; + m_attentionIcon = icon; + QStringList notificationActions; + switch (iconType) { + case Information: + m_attentionIconName = QStringLiteral("dialog-information"); + break; + case Warning: + m_attentionIconName = QStringLiteral("dialog-warning"); + break; + case Critical: + m_attentionIconName = QStringLiteral("dialog-error"); + // If there are actions, the desktop notification may appear as a message dialog + // with button(s), which will interrupt the user and require a response. + // That is an optional feature in implementations of org.freedesktop.Notifications + notificationActions << DefaultAction << tr("OK"); + break; + default: + m_attentionIconName.clear(); + break; + } + if (m_attentionIconName.isEmpty()) { + if (m_tempAttentionIcon) + delete m_tempAttentionIcon; + m_tempAttentionIcon = tempIcon(icon); + if (m_tempAttentionIcon) + m_attentionIconName = m_tempAttentionIcon->fileName(); + } + qCDebug(qLcTray) << title << msg << + QPlatformSystemTrayIcon::metaObject()->enumerator( + QPlatformSystemTrayIcon::staticMetaObject.indexOfEnumerator("MessageIcon")).valueToKey(iconType) + << m_attentionIconName << msecs; + setStatus(QStringLiteral("NeedsAttention")); + m_attentionTimer.start(msecs); + emit tooltipChanged(); + emit attention(); + + // Desktop notification + QVariantMap hints; + // urgency levels according to https://developer.gnome.org/notification-spec/#urgency-levels + // 0 low, 1 normal, 2 critical + int urgency = static_cast(iconType) - 1; + if (urgency < 0) // no icon + urgency = 0; + hints.insert(QLatin1String("urgency"), QVariant(urgency)); + m_notifier->notify(QCoreApplication::applicationName(), 0, + m_attentionIconName, title, msg, notificationActions, hints, msecs); +} + +void QDBusTrayIcon::actionInvoked(uint id, const QString &action) +{ + qCDebug(qLcTray) << id << action; + emit messageClicked(); +} + +void QDBusTrayIcon::notificationClosed(uint id, uint reason) +{ + qCDebug(qLcTray) << id << reason; +} + +bool QDBusTrayIcon::isSystemTrayAvailable() const +{ + QDBusMenuConnection * conn = const_cast(this)->dBusConnection(); + qCDebug(qLcTray) << conn->isStatusNotifierHostRegistered(); + return conn->isStatusNotifierHostRegistered(); +} + +QT_END_NAMESPACE +#endif //QT_NO_SYSTEMTRAYICON diff --git a/src/gui/platform/unix/dbustray/qdbustrayicon_p.h b/src/gui/platform/unix/dbustray/qdbustrayicon_p.h new file mode 100644 index 0000000000..04eefe3154 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qdbustrayicon_p.h @@ -0,0 +1,168 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + + +#ifndef QDBUSTRAYICON_H +#define QDBUSTRAYICON_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_REQUIRE_CONFIG(systemtrayicon); + +#include +#include +#include +#include "QtGui/qpa/qplatformsystemtrayicon.h" +#include "private/qdbusmenuconnection_p.h" + +QT_BEGIN_NAMESPACE + +class QStatusNotifierItemAdaptor; +class QDBusMenuAdaptor; +class QDBusPlatformMenu; +class QXdgNotificationInterface; + +class QDBusTrayIcon: public QPlatformSystemTrayIcon +{ + Q_OBJECT + Q_PROPERTY(QString category READ category NOTIFY categoryChanged) + Q_PROPERTY(QString status READ status NOTIFY statusChanged) + Q_PROPERTY(QString tooltip READ tooltip NOTIFY tooltipChanged) + Q_PROPERTY(QString iconName READ iconName NOTIFY iconChanged) + Q_PROPERTY(QIcon icon READ icon NOTIFY iconChanged) + Q_PROPERTY(bool isRequestingAttention READ isRequestingAttention NOTIFY attention) + Q_PROPERTY(QString attentionTitle READ attentionTitle NOTIFY attention) + Q_PROPERTY(QString attentionMessage READ attentionMessage NOTIFY attention) + Q_PROPERTY(QString attentionIconName READ attentionIconName NOTIFY attention) + Q_PROPERTY(QIcon attentionIcon READ attentionIcon NOTIFY attention) + Q_PROPERTY(QDBusPlatformMenu *menu READ menu NOTIFY menuChanged) + Q_MOC_INCLUDE() + +public: + QDBusTrayIcon(); + + virtual ~QDBusTrayIcon(); + + QDBusMenuConnection * dBusConnection(); + + void init() override; + void cleanup() override; + void updateIcon(const QIcon &icon) override; + void updateToolTip(const QString &tooltip) override; + void updateMenu(QPlatformMenu *menu) override; + QPlatformMenu *createMenu() const override; + void showMessage(const QString &title, const QString &msg, + const QIcon &icon, MessageIcon iconType, int msecs) override; + + bool isSystemTrayAvailable() const override; + bool supportsMessages() const override { return true; } + QRect geometry() const override { return QRect(); } + + QString category() const { return m_category; } + QString status() const { return m_status; } + QString tooltip() const { return m_tooltip; } + + QString iconName() const { return m_iconName; } + const QIcon & icon() const { return m_icon; } + + bool isRequestingAttention() const { return m_attentionTimer.isActive(); } + QString attentionTitle() const { return m_messageTitle; } + QString attentionMessage() const { return m_message; } + QString attentionIconName() const { return m_attentionIconName; } + const QIcon & attentionIcon() const { return m_attentionIcon; } + + QString instanceId() const { return m_instanceId; } + + QDBusPlatformMenu *menu() { return m_menu; } + +signals: + void categoryChanged(); + void statusChanged(QString arg); + void tooltipChanged(); + void iconChanged(); + void attention(); + void menuChanged(); + +private Q_SLOTS: + void attentionTimerExpired(); + void actionInvoked(uint id, const QString &action); + void notificationClosed(uint id, uint reason); + void watcherServiceRegistered(const QString &serviceName); + +private: + void setStatus(const QString &status); + QTemporaryFile *tempIcon(const QIcon &icon); + +private: + QDBusMenuConnection* m_dbusConnection; + QStatusNotifierItemAdaptor *m_adaptor; + QDBusMenuAdaptor *m_menuAdaptor; + QDBusPlatformMenu *m_menu; + QXdgNotificationInterface *m_notifier; + QString m_instanceId; + QString m_category; + QString m_defaultStatus; + QString m_status; + QString m_tooltip; + QString m_messageTitle; + QString m_message; + QIcon m_icon; + QTemporaryFile *m_tempIcon; + QString m_iconName; + QIcon m_attentionIcon; + QTemporaryFile *m_tempAttentionIcon; + QString m_attentionIconName; + QTimer m_attentionTimer; + bool m_registered; +}; + +QT_END_NAMESPACE + +#endif // QDBUSTRAYICON_H diff --git a/src/gui/platform/unix/dbustray/qdbustraytypes.cpp b/src/gui/platform/unix/dbustray/qdbustraytypes.cpp new file mode 100644 index 0000000000..97cc8b7f36 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qdbustraytypes.cpp @@ -0,0 +1,212 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Marco Martin +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QT_NO_SYSTEMTRAYICON + +#include "qdbustraytypes_p.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +static const int IconSizeLimit = 64; +static const int IconNormalSmallSize = 22; +static const int IconNormalMediumSize = 64; + +QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon) +{ + QXdgDBusImageVector ret; + QList sizes = icon.availableSizes(); + + // Omit any size larger than 64 px, to save D-Bus bandwidth; + // ensure that 22px or smaller exists, because it's a common size; + // and ensure that something between 22px and 64px exists, for better scaling to other sizes. + bool hasSmallIcon = false; + bool hasMediumIcon = false; + qreal dpr = qGuiApp->devicePixelRatio(); + QList toRemove; + for (const QSize &size : qAsConst(sizes)) { + int maxSize = qMax(size.width(), size.height()); + if (maxSize <= IconNormalSmallSize * dpr) + hasSmallIcon = true; + else if (maxSize <= IconNormalMediumSize * dpr) + hasMediumIcon = true; + else if (maxSize > IconSizeLimit * dpr) + toRemove << size; + } + for (const QSize &size : qAsConst(toRemove)) + sizes.removeOne(size); + if (!hasSmallIcon) + sizes.append(QSize(IconNormalSmallSize * dpr, IconNormalSmallSize * dpr)); + if (!hasMediumIcon) + sizes.append(QSize(IconNormalMediumSize * dpr, IconNormalMediumSize * dpr)); + + ret.reserve(sizes.size()); + for (const QSize &size : qAsConst(sizes)) { + // Protocol specifies ARGB32 format in network byte order + QImage im = icon.pixmap(size).toImage().convertToFormat(QImage::Format_ARGB32); + // letterbox if necessary to make it square + if (im.height() != im.width()) { + int maxSize = qMax(im.width(), im.height()); + QImage padded(maxSize, maxSize, QImage::Format_ARGB32); + padded.fill(Qt::transparent); + QPainter painter(&padded); + painter.drawImage((maxSize - im.width()) / 2, (maxSize - im.height()) / 2, im); + im = padded; + } + // copy and endian-convert + QXdgDBusImageStruct kim(im.width(), im.height()); + const uchar *end = im.constBits() + im.sizeInBytes(); + uchar *dest = reinterpret_cast(kim.data.data()); + for (const uchar *src = im.constBits(); src < end; src += 4, dest += 4) + qToUnaligned(qToBigEndian(qFromUnaligned(src)), dest); + + ret << kim; + } + return ret; +} + +// Marshall the ImageStruct data into a D-Bus argument +const QDBusArgument &operator<<(QDBusArgument &argument, const QXdgDBusImageStruct &icon) +{ + argument.beginStructure(); + argument << icon.width; + argument << icon.height; + argument << icon.data; + argument.endStructure(); + return argument; +} + +// Retrieve the ImageStruct data from the D-Bus argument +const QDBusArgument &operator>>(const QDBusArgument &argument, QXdgDBusImageStruct &icon) +{ + qint32 width; + qint32 height; + QByteArray data; + + argument.beginStructure(); + argument >> width; + argument >> height; + argument >> data; + argument.endStructure(); + + icon.width = width; + icon.height = height; + icon.data = data; + + return argument; +} + +// Marshall the ImageVector data into a D-Bus argument +const QDBusArgument &operator<<(QDBusArgument &argument, const QXdgDBusImageVector &iconVector) +{ + argument.beginArray(qMetaTypeId()); + for (int i = 0; i < iconVector.size(); ++i) { + argument << iconVector[i]; + } + argument.endArray(); + return argument; +} + +// Retrieve the ImageVector data from the D-Bus argument +const QDBusArgument &operator>>(const QDBusArgument &argument, QXdgDBusImageVector &iconVector) +{ + argument.beginArray(); + iconVector.clear(); + + while (!argument.atEnd()) { + QXdgDBusImageStruct element; + argument >> element; + iconVector.append(element); + } + + argument.endArray(); + + return argument; +} + +// Marshall the ToolTipStruct data into a D-Bus argument +const QDBusArgument &operator<<(QDBusArgument &argument, const QXdgDBusToolTipStruct &toolTip) +{ + argument.beginStructure(); + argument << toolTip.icon; + argument << toolTip.image; + argument << toolTip.title; + argument << toolTip.subTitle; + argument.endStructure(); + return argument; +} + +// Retrieve the ToolTipStruct data from the D-Bus argument +const QDBusArgument &operator>>(const QDBusArgument &argument, QXdgDBusToolTipStruct &toolTip) +{ + QString icon; + QXdgDBusImageVector image; + QString title; + QString subTitle; + + argument.beginStructure(); + argument >> icon; + argument >> image; + argument >> title; + argument >> subTitle; + argument.endStructure(); + + toolTip.icon = icon; + toolTip.image = image; + toolTip.title = title; + toolTip.subTitle = subTitle; + + return argument; +} + +QT_END_NAMESPACE +#endif // QT_NO_SYSTEMTRAYICON diff --git a/src/gui/platform/unix/dbustray/qdbustraytypes_p.h b/src/gui/platform/unix/dbustray/qdbustraytypes_p.h new file mode 100644 index 0000000000..3f75555579 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qdbustraytypes_p.h @@ -0,0 +1,109 @@ +/**************************************************************************** +** +** Copyright (C) 2009 Marco Martin +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QDBUSTRAYTYPES_P_H +#define QDBUSTRAYTYPES_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_REQUIRE_CONFIG(systemtrayicon); + +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +// Custom message type to send icons across D-Bus +struct QXdgDBusImageStruct +{ + QXdgDBusImageStruct() { } + QXdgDBusImageStruct(int w, int h) + : width(w), height(h), data(width * height * 4, 0) { } + int width; + int height; + QByteArray data; +}; +Q_DECLARE_TYPEINFO(QXdgDBusImageStruct, Q_MOVABLE_TYPE); + +typedef QVector QXdgDBusImageVector; + +QXdgDBusImageVector iconToQXdgDBusImageVector(const QIcon &icon); + +// Custom message type to send tooltips across D-Bus +struct QXdgDBusToolTipStruct +{ + QString icon; + QXdgDBusImageVector image; + QString title; + QString subTitle; +}; +Q_DECLARE_TYPEINFO(QXdgDBusToolTipStruct, Q_MOVABLE_TYPE); + +const QDBusArgument &operator<<(QDBusArgument &argument, const QXdgDBusImageStruct &icon); +const QDBusArgument &operator>>(const QDBusArgument &argument, QXdgDBusImageStruct &icon); + +const QDBusArgument &operator<<(QDBusArgument &argument, const QXdgDBusImageVector &iconVector); +const QDBusArgument &operator>>(const QDBusArgument &argument, QXdgDBusImageVector &iconVector); + +const QDBusArgument &operator<<(QDBusArgument &argument, const QXdgDBusToolTipStruct &toolTip); +const QDBusArgument &operator>>(const QDBusArgument &argument, QXdgDBusToolTipStruct &toolTip); + +QT_END_NAMESPACE + +Q_DECLARE_METATYPE(QXdgDBusImageStruct) +Q_DECLARE_METATYPE(QXdgDBusImageVector) +Q_DECLARE_METATYPE(QXdgDBusToolTipStruct) + +#endif // QDBUSTRAYTYPES_P_H diff --git a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp new file mode 100644 index 0000000000..ef2d330959 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor.cpp @@ -0,0 +1,189 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + This file was originally created by qdbusxml2cpp version 0.8 + Command line was: + qdbusxml2cpp -a statusnotifieritem ../../3rdparty/dbus-ifaces/org.kde.StatusNotifierItem.xml + + However it is maintained manually, because this adapter needs to do + significant interface adaptation, and can do it more efficiently using the + QDBusTrayIcon API directly rather than via QObject::property() and + QMetaObject::invokeMethod(). +*/ + +#include "qstatusnotifieritemadaptor_p.h" + +#ifndef QT_NO_SYSTEMTRAYICON + +#include +#include + +#include "qdbustrayicon_p.h" + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcMenu) +Q_DECLARE_LOGGING_CATEGORY(qLcTray) + +QStatusNotifierItemAdaptor::QStatusNotifierItemAdaptor(QDBusTrayIcon *parent) + : QDBusAbstractAdaptor(parent), m_trayIcon(parent) +{ + setAutoRelaySignals(true); +} + +QStatusNotifierItemAdaptor::~QStatusNotifierItemAdaptor() +{ +} + +QString QStatusNotifierItemAdaptor::attentionIconName() const +{ + return m_trayIcon->attentionIconName(); +} + +QXdgDBusImageVector QStatusNotifierItemAdaptor::attentionIconPixmap() const +{ + return iconToQXdgDBusImageVector(m_trayIcon->attentionIcon()); +} + +QString QStatusNotifierItemAdaptor::attentionMovieName() const +{ + return QString(); +} + +QString QStatusNotifierItemAdaptor::category() const +{ + return m_trayIcon->category(); +} + +QString QStatusNotifierItemAdaptor::iconName() const +{ + return m_trayIcon->iconName(); +} + +QXdgDBusImageVector QStatusNotifierItemAdaptor::iconPixmap() const +{ + return iconToQXdgDBusImageVector(m_trayIcon->icon()); +} + +QString QStatusNotifierItemAdaptor::id() const +{ + // from the API docs: "a name that should be unique for this application and + // consistent between sessions, such as the application name itself" + return QCoreApplication::applicationName(); +} + +bool QStatusNotifierItemAdaptor::itemIsMenu() const +{ + // From KDE docs: if this is true, the item only supports the context menu, + // so the visualization should prefer sending ContextMenu() instead of Activate(). + // But QSystemTrayIcon doesn't have such a setting: it will emit activated() + // and the application is free to use it or ignore it; we don't know whether it will. + return false; +} + +QDBusObjectPath QStatusNotifierItemAdaptor::menu() const +{ + return QDBusObjectPath(m_trayIcon->menu() ? "/MenuBar" : "/NO_DBUSMENU"); +} + +QString QStatusNotifierItemAdaptor::overlayIconName() const +{ + return QString(); +} + +QXdgDBusImageVector QStatusNotifierItemAdaptor::overlayIconPixmap() const +{ + QXdgDBusImageVector ret; // empty vector + return ret; +} + +QString QStatusNotifierItemAdaptor::status() const +{ + return m_trayIcon->status(); +} + +QString QStatusNotifierItemAdaptor::title() const +{ + // Shown e.g. when the icon is hidden, in the popup showing all hidden items. + // Since QSystemTrayIcon doesn't have this property, the application name + // is the best information we have available. + return QCoreApplication::applicationName(); +} + +QXdgDBusToolTipStruct QStatusNotifierItemAdaptor::toolTip() const +{ + QXdgDBusToolTipStruct ret; + if (m_trayIcon->isRequestingAttention()) { + ret.title = m_trayIcon->attentionTitle(); + ret.subTitle = m_trayIcon->attentionMessage(); + ret.icon = m_trayIcon->attentionIconName(); + } else { + ret.title = m_trayIcon->tooltip(); + } + return ret; +} + +void QStatusNotifierItemAdaptor::Activate(int x, int y) +{ + qCDebug(qLcTray) << x << y; + emit m_trayIcon->activated(QPlatformSystemTrayIcon::Trigger); +} + +void QStatusNotifierItemAdaptor::ContextMenu(int x, int y) +{ + qCDebug(qLcTray) << x << y; + emit m_trayIcon->activated(QPlatformSystemTrayIcon::Context); +} + +void QStatusNotifierItemAdaptor::Scroll(int w, const QString &s) +{ + qCDebug(qLcTray) << w << s; + // unsupported +} + +void QStatusNotifierItemAdaptor::SecondaryActivate(int x, int y) +{ + qCDebug(qLcTray) << x << y; + emit m_trayIcon->activated(QPlatformSystemTrayIcon::MiddleClick); +} + +QT_END_NAMESPACE + +#endif // QT_NO_SYSTEMTRAYICON diff --git a/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h new file mode 100644 index 0000000000..f2bb156b1d --- /dev/null +++ b/src/gui/platform/unix/dbustray/qstatusnotifieritemadaptor_p.h @@ -0,0 +1,206 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + This file was originally created by qdbusxml2cpp version 0.8 + Command line was: + qdbusxml2cpp -a statusnotifieritem ../../3rdparty/dbus-ifaces/org.kde.StatusNotifierItem.xml + + However it is maintained manually. + + It is also not part of the public API. This header file may change from + version to version without notice, or even be removed. +*/ + +#ifndef QSTATUSNOTIFIERITEMADAPTER_P_H +#define QSTATUSNOTIFIERITEMADAPTER_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include + +QT_REQUIRE_CONFIG(systemtrayicon); + +#include +#include + +#include "qdbustraytypes_p.h" + +QT_BEGIN_NAMESPACE +class QDBusTrayIcon; + +/* + Adaptor class for interface org.kde.StatusNotifierItem + see http://www.freedesktop.org/wiki/Specifications/StatusNotifierItem/ + (also http://www.notmart.org/misc/statusnotifieritem/) +*/ +class QStatusNotifierItemAdaptor: public QDBusAbstractAdaptor +{ + Q_OBJECT + Q_CLASSINFO("D-Bus Interface", "org.kde.StatusNotifierItem") + Q_CLASSINFO("D-Bus Introspection", "" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" +" \n" + "") +public: + QStatusNotifierItemAdaptor(QDBusTrayIcon *parent); + virtual ~QStatusNotifierItemAdaptor(); + +public: // PROPERTIES + Q_PROPERTY(QString AttentionIconName READ attentionIconName) + QString attentionIconName() const; + + Q_PROPERTY(QXdgDBusImageVector AttentionIconPixmap READ attentionIconPixmap) + QXdgDBusImageVector attentionIconPixmap() const; + + Q_PROPERTY(QString AttentionMovieName READ attentionMovieName) + QString attentionMovieName() const; + + Q_PROPERTY(QString Category READ category) + QString category() const; + + Q_PROPERTY(QString IconName READ iconName) + QString iconName() const; + + Q_PROPERTY(QXdgDBusImageVector IconPixmap READ iconPixmap) + QXdgDBusImageVector iconPixmap() const; + + Q_PROPERTY(QString Id READ id) + QString id() const; + + Q_PROPERTY(bool ItemIsMenu READ itemIsMenu) + bool itemIsMenu() const; + + Q_PROPERTY(QDBusObjectPath Menu READ menu) + QDBusObjectPath menu() const; + + Q_PROPERTY(QString OverlayIconName READ overlayIconName) + QString overlayIconName() const; + + Q_PROPERTY(QXdgDBusImageVector OverlayIconPixmap READ overlayIconPixmap) + QXdgDBusImageVector overlayIconPixmap() const; + + Q_PROPERTY(QString Status READ status) + QString status() const; + + Q_PROPERTY(QString Title READ title) + QString title() const; + + Q_PROPERTY(QXdgDBusToolTipStruct ToolTip READ toolTip) + QXdgDBusToolTipStruct toolTip() const; + +public Q_SLOTS: // METHODS + void Activate(int x, int y); + void ContextMenu(int x, int y); + void Scroll(int delta, const QString &orientation); + void SecondaryActivate(int x, int y); +Q_SIGNALS: // SIGNALS + void NewAttentionIcon(); + void NewIcon(); + void NewOverlayIcon(); + void NewMenu(); + void NewStatus(const QString &status); + void NewTitle(); + void NewToolTip(); + +private: + QDBusTrayIcon *m_trayIcon; +}; + +QT_END_NAMESPACE + +#endif // QSTATUSNOTIFIERITEMADAPTER_P_H diff --git a/src/gui/platform/unix/dbustray/qxdgnotificationproxy.cpp b/src/gui/platform/unix/dbustray/qxdgnotificationproxy.cpp new file mode 100644 index 0000000000..ef2aa799c8 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qxdgnotificationproxy.cpp @@ -0,0 +1,53 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qxdgnotificationproxy_p.h" + +QT_BEGIN_NAMESPACE + +QXdgNotificationInterface::QXdgNotificationInterface(const QString &service, const QString &path, const QDBusConnection &connection, QObject *parent) + : QDBusAbstractInterface(service, path, staticInterfaceName(), connection, parent) +{ +} + +QXdgNotificationInterface::~QXdgNotificationInterface() +{ +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h b/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h new file mode 100644 index 0000000000..495208f873 --- /dev/null +++ b/src/gui/platform/unix/dbustray/qxdgnotificationproxy_p.h @@ -0,0 +1,143 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the QtGui module of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +/* + This file was originally created by qdbusxml2cpp version 0.8 + Command line was: + qdbusxml2cpp -p qxdgnotificationproxy ../../3rdparty/dbus-ifaces/org.freedesktop.Notifications.xml + + However it is maintained manually. + + It is also not part of the public API. This header file may change from + version to version without notice, or even be removed. +*/ + +#ifndef QXDGNOTIFICATIONPROXY_P_H +#define QXDGNOTIFICATIONPROXY_P_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists for the convenience +// of other Qt classes. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcTray) + +/* + * Proxy class for interface org.freedesktop.Notifications + */ +class QXdgNotificationInterface: public QDBusAbstractInterface +{ + Q_OBJECT +public: + static inline const char *staticInterfaceName() + { return "org.freedesktop.Notifications"; } + +public: + QXdgNotificationInterface(const QString &service, const QString &path, + const QDBusConnection &connection, QObject *parent = nullptr); + + ~QXdgNotificationInterface(); + +public Q_SLOTS: // METHODS + inline QDBusPendingReply<> closeNotification(uint id) + { + return asyncCall(QStringLiteral("CloseNotification"), id); + } + + inline QDBusPendingReply getCapabilities() + { + return asyncCall(QStringLiteral("GetCapabilities")); + } + + inline QDBusPendingReply getServerInformation() + { + return asyncCall(QStringLiteral("GetServerInformation")); + } + inline QDBusReply getServerInformation(QString &vendor, QString &version, QString &specVersion) + { + QDBusMessage reply = call(QDBus::Block, QStringLiteral("GetServerInformation")); + if (reply.type() == QDBusMessage::ReplyMessage && reply.arguments().count() == 4) { + vendor = qdbus_cast(reply.arguments().at(1)); + version = qdbus_cast(reply.arguments().at(2)); + specVersion = qdbus_cast(reply.arguments().at(3)); + } + return reply; + } + + // see https://developer.gnome.org/notification-spec/#basic-design + inline QDBusPendingReply notify(const QString &appName, uint replacesId, const QString &appIcon, + const QString &summary, const QString &body, const QStringList &actions, + const QVariantMap &hints, int timeout) + { + qCDebug(qLcTray) << appName << replacesId << appIcon << summary << body << actions << hints << timeout; + return asyncCall(QStringLiteral("Notify"), appName, replacesId, appIcon, summary, body, actions, hints, timeout); + } + +Q_SIGNALS: + void ActionInvoked(uint id, const QString &action_key); + void NotificationClosed(uint id, uint reason); +}; + +QT_END_NAMESPACE + +namespace org { + namespace freedesktop { + using Notifications = QT_PREPEND_NAMESPACE(QXdgNotificationInterface); + } +} + +#endif diff --git a/src/gui/platform/unix/qgenericunixthemes.cpp b/src/gui/platform/unix/qgenericunixthemes.cpp new file mode 100644 index 0000000000..352c975400 --- /dev/null +++ b/src/gui/platform/unix/qgenericunixthemes.cpp @@ -0,0 +1,884 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#include "qgenericunixthemes_p.h" + +#include "qpa/qplatformtheme_p.h" +#include "qpa/qplatformfontdatabase.h" // lcQpaFonts + +#include +#include +#include +#include +#include +#include +#include +#include +#if QT_CONFIG(mimetype) +#include +#endif +#include +#if QT_CONFIG(settings) +#include +#endif +#include +#include +#include +#include +#include +#include +#include +#ifndef QT_NO_DBUS +#include +#include +#endif +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) +#include +#endif + +#include + +QT_BEGIN_NAMESPACE + +Q_DECLARE_LOGGING_CATEGORY(qLcTray) + +ResourceHelper::ResourceHelper() +{ + std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast(0)); + std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast(0)); +} + +void ResourceHelper::clear() +{ + qDeleteAll(palettes, palettes + QPlatformTheme::NPalettes); + qDeleteAll(fonts, fonts + QPlatformTheme::NFonts); + std::fill(palettes, palettes + QPlatformTheme::NPalettes, static_cast(0)); + std::fill(fonts, fonts + QPlatformTheme::NFonts, static_cast(0)); +} + +const char *QGenericUnixTheme::name = "generic"; + +// Default system font, corresponding to the value returned by 4.8 for +// XRender/FontConfig which we can now assume as default. +static const char defaultSystemFontNameC[] = "Sans Serif"; +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; +} +#endif + +#ifndef QT_NO_DBUS +static bool checkDBusGlobalMenuAvailable() +{ + const QDBusConnection connection = QDBusConnection::sessionBus(); + static const QString registrarService = QStringLiteral("com.canonical.AppMenu.Registrar"); + if (const auto iface = connection.interface()) + return iface->isServiceRegistered(registrarService); + return false; +} + +static bool isDBusGlobalMenuAvailable() +{ + static bool dbusGlobalMenuAvailable = checkDBusGlobalMenuAvailable(); + return dbusGlobalMenuAvailable; +} +#endif + +class QGenericUnixThemePrivate : public QPlatformThemePrivate +{ +public: + QGenericUnixThemePrivate() + : QPlatformThemePrivate() + , systemFont(QLatin1String(defaultSystemFontNameC), defaultSystemFontSize) + , fixedFont(QLatin1String(defaultFixedFontNameC), systemFont.pointSize()) + { + fixedFont.setStyleHint(QFont::TypeWriter); + qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont; + } + + const QFont systemFont; + QFont fixedFont; +}; + +QGenericUnixTheme::QGenericUnixTheme() + : QPlatformTheme(new QGenericUnixThemePrivate()) +{ +} + +const QFont *QGenericUnixTheme::font(Font type) const +{ + Q_D(const QGenericUnixTheme); + switch (type) { + case QPlatformTheme::SystemFont: + return &d->systemFont; + case QPlatformTheme::FixedFont: + return &d->fixedFont; + default: + return 0; + } +} + +// Helper to return the icon theme paths from XDG. +QStringList QGenericUnixTheme::xdgIconThemePaths() +{ + QStringList paths; + // Add home directory first in search path + const QFileInfo homeIconDir(QDir::homePath() + QLatin1String("/.icons")); + if (homeIconDir.isDir()) + paths.prepend(homeIconDir.absoluteFilePath()); + + paths.append(QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, + QStringLiteral("icons"), + QStandardPaths::LocateDirectory)); + + return paths; +} + +QStringList QGenericUnixTheme::iconFallbackPaths() +{ + QStringList paths; + const QFileInfo pixmapsIconsDir(QStringLiteral("/usr/share/pixmaps")); + if (pixmapsIconsDir.isDir()) + paths.append(pixmapsIconsDir.absoluteFilePath()); + + return paths; +} + +#ifndef QT_NO_DBUS +QPlatformMenuBar *QGenericUnixTheme::createPlatformMenuBar() const +{ + if (isDBusGlobalMenuAvailable()) + return new QDBusMenuBar(); + return nullptr; +} +#endif + +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) +QPlatformSystemTrayIcon *QGenericUnixTheme::createPlatformSystemTrayIcon() const +{ + if (isDBusTrayAvailable()) + return new QDBusTrayIcon(); + return nullptr; +} +#endif + +QVariant QGenericUnixTheme::themeHint(ThemeHint hint) const +{ + switch (hint) { + case QPlatformTheme::SystemIconFallbackThemeName: + return QVariant(QString(QStringLiteral("hicolor"))); + case QPlatformTheme::IconThemeSearchPaths: + return xdgIconThemePaths(); + case QPlatformTheme::IconFallbackSearchPaths: + return iconFallbackPaths(); + case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: + return QVariant(true); + case QPlatformTheme::StyleNames: { + QStringList styleNames; + styleNames << QStringLiteral("Fusion") << QStringLiteral("Windows"); + return QVariant(styleNames); + } + case QPlatformTheme::KeyboardScheme: + return QVariant(int(X11KeyboardScheme)); + case QPlatformTheme::UiEffects: + return QVariant(int(HoverEffect)); + default: + break; + } + return QPlatformTheme::themeHint(hint); +} + +// Helper functions for implementing QPlatformTheme::fileIcon() for XDG icon themes. +static QList availableXdgFileIconSizes() +{ + return QIcon::fromTheme(QStringLiteral("inode-directory")).availableSizes(); +} + +#if QT_CONFIG(mimetype) +static QIcon xdgFileIcon(const QFileInfo &fileInfo) +{ + QMimeDatabase mimeDatabase; + QMimeType mimeType = mimeDatabase.mimeTypeForFile(fileInfo); + if (!mimeType.isValid()) + return QIcon(); + const QString &iconName = mimeType.iconName(); + if (!iconName.isEmpty()) { + const QIcon icon = QIcon::fromTheme(iconName); + if (!icon.isNull()) + return icon; + } + const QString &genericIconName = mimeType.genericIconName(); + return genericIconName.isEmpty() ? QIcon() : QIcon::fromTheme(genericIconName); +} +#endif + +#if QT_CONFIG(settings) +class QKdeThemePrivate : public QPlatformThemePrivate +{ +public: + QKdeThemePrivate(const QStringList &kdeDirs, int kdeVersion) + : kdeDirs(kdeDirs) + , kdeVersion(kdeVersion) + { } + + static QString kdeGlobals(const QString &kdeDir, int kdeVersion) + { + if (kdeVersion > 4) + return kdeDir + QLatin1String("/kdeglobals"); + return kdeDir + QLatin1String("/share/config/kdeglobals"); + } + + void refresh(); + static QVariant readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash &kdeSettings); + static void readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash &kdeSettings, QPalette *pal); + static QFont *kdeFont(const QVariant &fontValue); + static QStringList kdeIconThemeSearchPaths(const QStringList &kdeDirs); + + const QStringList kdeDirs; + const int kdeVersion; + + ResourceHelper resources; + QString iconThemeName; + QString iconFallbackThemeName; + QStringList styleNames; + int toolButtonStyle = Qt::ToolButtonTextBesideIcon; + int toolBarIconSize = 0; + bool singleClick = true; + bool showIconsOnPushButtons = true; + int wheelScrollLines = 3; + int doubleClickInterval = 400; + int startDragDist = 10; + int startDragTime = 500; + int cursorBlinkRate = 1000; +}; + +void QKdeThemePrivate::refresh() +{ + resources.clear(); + + toolButtonStyle = Qt::ToolButtonTextBesideIcon; + toolBarIconSize = 0; + styleNames.clear(); + if (kdeVersion >= 5) + styleNames << QStringLiteral("breeze"); + styleNames << QStringLiteral("Oxygen") << QStringLiteral("fusion") << QStringLiteral("windows"); + if (kdeVersion >= 5) + iconFallbackThemeName = iconThemeName = QStringLiteral("breeze"); + else + iconFallbackThemeName = iconThemeName = QStringLiteral("oxygen"); + + QHash 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); + if (styleValue.isValid()) { + const QString style = styleValue.toString(); + if (style != styleNames.front()) + styleNames.push_front(style); + } + + const QVariant singleClickValue = readKdeSetting(QStringLiteral("KDE/SingleClick"), kdeDirs, kdeVersion, kdeSettings); + if (singleClickValue.isValid()) + singleClick = singleClickValue.toBool(); + + const QVariant showIconsOnPushButtonsValue = readKdeSetting(QStringLiteral("KDE/ShowIconsOnPushButtons"), kdeDirs, kdeVersion, kdeSettings); + if (showIconsOnPushButtonsValue.isValid()) + showIconsOnPushButtons = showIconsOnPushButtonsValue.toBool(); + + const QVariant themeValue = readKdeSetting(QStringLiteral("Icons/Theme"), kdeDirs, kdeVersion, kdeSettings); + if (themeValue.isValid()) + iconThemeName = themeValue.toString(); + + const QVariant toolBarIconSizeValue = readKdeSetting(QStringLiteral("ToolbarIcons/Size"), kdeDirs, kdeVersion, kdeSettings); + if (toolBarIconSizeValue.isValid()) + toolBarIconSize = toolBarIconSizeValue.toInt(); + + const QVariant toolbarStyleValue = readKdeSetting(QStringLiteral("Toolbar style/ToolButtonStyle"), kdeDirs, kdeVersion, kdeSettings); + if (toolbarStyleValue.isValid()) { + const QString toolBarStyle = toolbarStyleValue.toString(); + if (toolBarStyle == QLatin1String("TextBesideIcon")) + toolButtonStyle = Qt::ToolButtonTextBesideIcon; + else if (toolBarStyle == QLatin1String("TextOnly")) + toolButtonStyle = Qt::ToolButtonTextOnly; + else if (toolBarStyle == QLatin1String("TextUnderIcon")) + toolButtonStyle = Qt::ToolButtonTextUnderIcon; + } + + const QVariant wheelScrollLinesValue = readKdeSetting(QStringLiteral("KDE/WheelScrollLines"), kdeDirs, kdeVersion, kdeSettings); + if (wheelScrollLinesValue.isValid()) + wheelScrollLines = wheelScrollLinesValue.toInt(); + + const QVariant doubleClickIntervalValue = readKdeSetting(QStringLiteral("KDE/DoubleClickInterval"), kdeDirs, kdeVersion, kdeSettings); + if (doubleClickIntervalValue.isValid()) + doubleClickInterval = doubleClickIntervalValue.toInt(); + + const QVariant startDragDistValue = readKdeSetting(QStringLiteral("KDE/StartDragDist"), kdeDirs, kdeVersion, kdeSettings); + if (startDragDistValue.isValid()) + startDragDist = startDragDistValue.toInt(); + + const QVariant startDragTimeValue = readKdeSetting(QStringLiteral("KDE/StartDragTime"), kdeDirs, kdeVersion, kdeSettings); + if (startDragTimeValue.isValid()) + startDragTime = startDragTimeValue.toInt(); + + const QVariant cursorBlinkRateValue = readKdeSetting(QStringLiteral("KDE/CursorBlinkRate"), kdeDirs, kdeVersion, kdeSettings); + 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))) + resources.fonts[QPlatformTheme::SystemFont] = systemFont; + else + resources.fonts[QPlatformTheme::SystemFont] = new QFont(QLatin1String(defaultSystemFontNameC), defaultSystemFontSize); + + if (QFont *fixedFont = kdeFont(readKdeSetting(QStringLiteral("fixed"), kdeDirs, kdeVersion, kdeSettings))) { + resources.fonts[QPlatformTheme::FixedFont] = fixedFont; + } else { + fixedFont = new QFont(QLatin1String(defaultFixedFontNameC), defaultSystemFontSize); + fixedFont->setStyleHint(QFont::TypeWriter); + resources.fonts[QPlatformTheme::FixedFont] = fixedFont; + } + + if (QFont *menuFont = kdeFont(readKdeSetting(QStringLiteral("menuFont"), kdeDirs, kdeVersion, kdeSettings))) { + resources.fonts[QPlatformTheme::MenuFont] = menuFont; + resources.fonts[QPlatformTheme::MenuBarFont] = new QFont(*menuFont); + } + + if (QFont *toolBarFont = kdeFont(readKdeSetting(QStringLiteral("toolBarFont"), kdeDirs, kdeVersion, kdeSettings))) + resources.fonts[QPlatformTheme::ToolButtonFont] = toolBarFont; + + qCDebug(lcQpaFonts) << "default fonts: system" << resources.fonts[QPlatformTheme::SystemFont] + << "fixed" << resources.fonts[QPlatformTheme::FixedFont]; + qDeleteAll(kdeSettings); +} + +QVariant QKdeThemePrivate::readKdeSetting(const QString &key, const QStringList &kdeDirs, int kdeVersion, QHash &kdeSettings) +{ + for (const QString &kdeDir : kdeDirs) { + QSettings *settings = kdeSettings.value(kdeDir); + if (!settings) { + const QString kdeGlobalsPath = kdeGlobals(kdeDir, kdeVersion); + if (QFileInfo(kdeGlobalsPath).isReadable()) { + settings = new QSettings(kdeGlobalsPath, QSettings::IniFormat); + kdeSettings.insert(kdeDir, settings); + } + } + if (settings) { + const QVariant value = settings->value(key); + if (value.isValid()) + return value; + } + } + return QVariant(); +} + +// 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) +{ + if (!value.isValid()) + return false; + const QStringList values = value.toStringList(); + if (values.size() != 3) + return false; + pal->setBrush(role, QColor(values.at(0).toInt(), values.at(1).toInt(), values.at(2).toInt())); + return true; +} + +void QKdeThemePrivate::readKdeSystemPalette(const QStringList &kdeDirs, int kdeVersion, QHash &kdeSettings, QPalette *pal) +{ + if (!kdeColor(pal, QPalette::Button, readKdeSetting(QStringLiteral("Colors:Button/BackgroundNormal"), kdeDirs, kdeVersion, kdeSettings))) { + // kcolorscheme.cpp: SetDefaultColors + const QColor defaultWindowBackground(214, 210, 208); + const QColor defaultButtonBackground(223, 220, 217); + *pal = QPalette(defaultButtonBackground, defaultWindowBackground); + 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)); + + // 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. + // We use a bit simpler approach here, similar logic than in qt_palette_from_color(). + const QColor button = pal->color(QPalette::Button); + int h, s, v; + button.getHsv(&h, &s, &v); + + const QBrush whiteBrush = QBrush(Qt::white); + const QBrush buttonBrush = QBrush(button); + const QBrush buttonBrushDark = QBrush(button.darker(v > 128 ? 200 : 50)); + const QBrush buttonBrushDark150 = QBrush(button.darker(v > 128 ? 150 : 75)); + const QBrush buttonBrushLight150 = QBrush(button.lighter(v > 128 ? 150 : 75)); + const QBrush buttonBrushLight = QBrush(button.lighter(v > 128 ? 200 : 50)); + + pal->setBrush(QPalette::Disabled, QPalette::WindowText, buttonBrushDark); + pal->setBrush(QPalette::Disabled, QPalette::ButtonText, buttonBrushDark); + pal->setBrush(QPalette::Disabled, QPalette::Button, buttonBrush); + pal->setBrush(QPalette::Disabled, QPalette::Text, buttonBrushDark); + pal->setBrush(QPalette::Disabled, QPalette::BrightText, whiteBrush); + pal->setBrush(QPalette::Disabled, QPalette::Base, buttonBrush); + pal->setBrush(QPalette::Disabled, QPalette::Window, buttonBrush); + pal->setBrush(QPalette::Disabled, QPalette::Highlight, buttonBrushDark150); + pal->setBrush(QPalette::Disabled, QPalette::HighlightedText, buttonBrushLight150); + + // set calculated colors for all groups + pal->setBrush(QPalette::Light, buttonBrushLight); + pal->setBrush(QPalette::Midlight, buttonBrushLight150); + pal->setBrush(QPalette::Mid, buttonBrushDark150); + pal->setBrush(QPalette::Dark, buttonBrushDark); +} + +/*! + \class QKdeTheme + \brief QKdeTheme is a theme implementation for the KDE desktop (version 4 or higher). + \since 5.0 + \internal + \ingroup qpa +*/ + +const char *QKdeTheme::name = "kde"; + +QKdeTheme::QKdeTheme(const QStringList& kdeDirs, int kdeVersion) + : QPlatformTheme(new QKdeThemePrivate(kdeDirs,kdeVersion)) +{ + d_func()->refresh(); +} + +QFont *QKdeThemePrivate::kdeFont(const QVariant &fontValue) +{ + if (fontValue.isValid()) { + // Read font value: Might be a QStringList as KDE stores fonts without quotes. + // Also retrieve the family for the constructor since we cannot use the + // default constructor of QFont, which accesses QGuiApplication::systemFont() + // causing recursion. + QString fontDescription; + QString fontFamily; + if (fontValue.userType() == QMetaType::QStringList) { + const QStringList list = fontValue.toStringList(); + if (!list.isEmpty()) { + fontFamily = list.first(); + fontDescription = list.join(QLatin1Char(',')); + } + } else { + fontDescription = fontFamily = fontValue.toString(); + } + if (!fontDescription.isEmpty()) { + QFont font(fontFamily); + if (font.fromString(fontDescription)) + return new QFont(font); + } + } + return 0; +} + + +QStringList QKdeThemePrivate::kdeIconThemeSearchPaths(const QStringList &kdeDirs) +{ + QStringList paths = QGenericUnixTheme::xdgIconThemePaths(); + const QString iconPath = QStringLiteral("/share/icons"); + for (const QString &candidate : kdeDirs) { + const QFileInfo fi(candidate + iconPath); + if (fi.isDir()) + paths.append(fi.absoluteFilePath()); + } + return paths; +} + +QVariant QKdeTheme::themeHint(QPlatformTheme::ThemeHint hint) const +{ + Q_D(const QKdeTheme); + switch (hint) { + case QPlatformTheme::UseFullScreenForPopupMenu: + return QVariant(true); + case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: + return QVariant(d->showIconsOnPushButtons); + case QPlatformTheme::DialogButtonBoxLayout: + return QVariant(QPlatformDialogHelper::KdeLayout); + case QPlatformTheme::ToolButtonStyle: + return QVariant(d->toolButtonStyle); + case QPlatformTheme::ToolBarIconSize: + return QVariant(d->toolBarIconSize); + case QPlatformTheme::SystemIconThemeName: + return QVariant(d->iconThemeName); + case QPlatformTheme::SystemIconFallbackThemeName: + return QVariant(d->iconFallbackThemeName); + case QPlatformTheme::IconThemeSearchPaths: + return QVariant(d->kdeIconThemeSearchPaths(d->kdeDirs)); + case QPlatformTheme::IconPixmapSizes: + return QVariant::fromValue(availableXdgFileIconSizes()); + case QPlatformTheme::StyleNames: + return QVariant(d->styleNames); + case QPlatformTheme::KeyboardScheme: + return QVariant(int(KdeKeyboardScheme)); + case QPlatformTheme::ItemViewActivateItemOnSingleClick: + return QVariant(d->singleClick); + case QPlatformTheme::WheelScrollLines: + return QVariant(d->wheelScrollLines); + case QPlatformTheme::MouseDoubleClickInterval: + return QVariant(d->doubleClickInterval); + case QPlatformTheme::StartDragTime: + return QVariant(d->startDragTime); + case QPlatformTheme::StartDragDistance: + return QVariant(d->startDragDist); + case QPlatformTheme::CursorFlashTime: + return QVariant(d->cursorBlinkRate); + case QPlatformTheme::UiEffects: + return QVariant(int(HoverEffect)); + default: + break; + } + return QPlatformTheme::themeHint(hint); +} + +QIcon QKdeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const +{ +#if QT_CONFIG(mimetype) + return xdgFileIcon(fileInfo); +#else + Q_UNUSED(fileInfo); + return QIcon(); +#endif +} + +const QPalette *QKdeTheme::palette(Palette type) const +{ + Q_D(const QKdeTheme); + return d->resources.palettes[type]; +} + +const QFont *QKdeTheme::font(Font type) const +{ + Q_D(const QKdeTheme); + return d->resources.fonts[type]; +} + +QPlatformTheme *QKdeTheme::createKdeTheme() +{ + const QByteArray kdeVersionBA = qgetenv("KDE_SESSION_VERSION"); + const int kdeVersion = kdeVersionBA.toInt(); + if (kdeVersion < 4) + return 0; + + if (kdeVersion > 4) + // Plasma 5 follows XDG spec + // but uses the same config file format: + return new QKdeTheme(QStandardPaths::standardLocations(QStandardPaths::GenericConfigLocation), kdeVersion); + + // Determine KDE prefixes in the following priority order: + // - KDEHOME and KDEDIRS environment variables + // - ~/.kde() + // - read prefixes from /etc/kderc + // - fallback to /etc/kde + + QStringList kdeDirs; + const QString kdeHomePathVar = QFile::decodeName(qgetenv("KDEHOME")); + if (!kdeHomePathVar.isEmpty()) + kdeDirs += kdeHomePathVar; + + const QString kdeDirsVar = QFile::decodeName(qgetenv("KDEDIRS")); + if (!kdeDirsVar.isEmpty()) + kdeDirs += kdeDirsVar.split(QLatin1Char(':'), Qt::SkipEmptyParts); + + const QString kdeVersionHomePath = QDir::homePath() + QLatin1String("/.kde") + QLatin1String(kdeVersionBA); + if (QFileInfo(kdeVersionHomePath).isDir()) + kdeDirs += kdeVersionHomePath; + + const QString kdeHomePath = QDir::homePath() + QLatin1String("/.kde"); + if (QFileInfo(kdeHomePath).isDir()) + kdeDirs += kdeHomePath; + + const QString kdeRcPath = QLatin1String("/etc/kde") + QLatin1String(kdeVersionBA) + QLatin1String("rc"); + if (QFileInfo(kdeRcPath).isReadable()) { + QSettings kdeSettings(kdeRcPath, QSettings::IniFormat); + kdeSettings.beginGroup(QStringLiteral("Directories-default")); + kdeDirs += kdeSettings.value(QStringLiteral("prefixes")).toStringList(); + } + + const QString kdeVersionPrefix = QLatin1String("/etc/kde") + QLatin1String(kdeVersionBA); + if (QFileInfo(kdeVersionPrefix).isDir()) + kdeDirs += kdeVersionPrefix; + + kdeDirs.removeDuplicates(); + if (kdeDirs.isEmpty()) { + qWarning("Unable to determine KDE dirs"); + return 0; + } + + return new QKdeTheme(kdeDirs, kdeVersion); +} + +#ifndef QT_NO_DBUS +QPlatformMenuBar *QKdeTheme::createPlatformMenuBar() const +{ + if (isDBusGlobalMenuAvailable()) + return new QDBusMenuBar(); + return nullptr; +} +#endif + +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) +QPlatformSystemTrayIcon *QKdeTheme::createPlatformSystemTrayIcon() const +{ + if (isDBusTrayAvailable()) + return new QDBusTrayIcon(); + return nullptr; +} +#endif + +#endif // settings + +/*! + \class QGnomeTheme + \brief QGnomeTheme is a theme implementation for the Gnome desktop. + \since 5.0 + \internal + \ingroup qpa +*/ + +const char *QGnomeTheme::name = "gnome"; + +class QGnomeThemePrivate : public QPlatformThemePrivate +{ +public: + QGnomeThemePrivate() : systemFont(nullptr), fixedFont(nullptr) {} + ~QGnomeThemePrivate() { delete systemFont; delete fixedFont; } + + void configureFonts(const QString >kFontName) const + { + Q_ASSERT(!systemFont); + const int split = gtkFontName.lastIndexOf(QChar::Space); + float size = QStringView{gtkFontName}.mid(split + 1).toFloat(); + QString fontName = gtkFontName.left(split); + + systemFont = new QFont(fontName, size); + fixedFont = new QFont(QLatin1String(defaultFixedFontNameC), systemFont->pointSize()); + fixedFont->setStyleHint(QFont::TypeWriter); + qCDebug(lcQpaFonts) << "default fonts: system" << systemFont << "fixed" << fixedFont; + } + + mutable QFont *systemFont; + mutable QFont *fixedFont; +}; + +QGnomeTheme::QGnomeTheme() + : QPlatformTheme(new QGnomeThemePrivate()) +{ +} + +QVariant QGnomeTheme::themeHint(QPlatformTheme::ThemeHint hint) const +{ + switch (hint) { + case QPlatformTheme::DialogButtonBoxButtonsHaveIcons: + return QVariant(true); + case QPlatformTheme::DialogButtonBoxLayout: + return QVariant(QPlatformDialogHelper::GnomeLayout); + case QPlatformTheme::SystemIconThemeName: + return QVariant(QStringLiteral("Adwaita")); + case QPlatformTheme::SystemIconFallbackThemeName: + return QVariant(QStringLiteral("gnome")); + case QPlatformTheme::IconThemeSearchPaths: + return QVariant(QGenericUnixTheme::xdgIconThemePaths()); + case QPlatformTheme::IconPixmapSizes: + return QVariant::fromValue(availableXdgFileIconSizes()); + case QPlatformTheme::StyleNames: { + QStringList styleNames; + styleNames << QStringLiteral("fusion") << QStringLiteral("windows"); + return QVariant(styleNames); + } + case QPlatformTheme::KeyboardScheme: + return QVariant(int(GnomeKeyboardScheme)); + case QPlatformTheme::PasswordMaskCharacter: + return QVariant(QChar(0x2022)); + case QPlatformTheme::UiEffects: + return QVariant(int(HoverEffect)); + default: + break; + } + return QPlatformTheme::themeHint(hint); +} + +QIcon QGnomeTheme::fileIcon(const QFileInfo &fileInfo, QPlatformTheme::IconOptions) const +{ +#if QT_CONFIG(mimetype) + return xdgFileIcon(fileInfo); +#else + Q_UNUSED(fileInfo); + return QIcon(); +#endif +} + +const QFont *QGnomeTheme::font(Font type) const +{ + Q_D(const QGnomeTheme); + if (!d->systemFont) + d->configureFonts(gtkFontName()); + switch (type) { + case QPlatformTheme::SystemFont: + return d->systemFont; + case QPlatformTheme::FixedFont: + return d->fixedFont; + default: + return 0; + } +} + +QString QGnomeTheme::gtkFontName() const +{ + return QStringLiteral("%1 %2").arg(QLatin1String(defaultSystemFontNameC)).arg(defaultSystemFontSize); +} + +#ifndef QT_NO_DBUS +QPlatformMenuBar *QGnomeTheme::createPlatformMenuBar() const +{ + if (isDBusGlobalMenuAvailable()) + return new QDBusMenuBar(); + return nullptr; +} +#endif + +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) +QPlatformSystemTrayIcon *QGnomeTheme::createPlatformSystemTrayIcon() const +{ + if (isDBusTrayAvailable()) + return new QDBusTrayIcon(); + return nullptr; +} +#endif + +QString QGnomeTheme::standardButtonText(int button) const +{ + switch (button) { + case QPlatformDialogHelper::Ok: + return QCoreApplication::translate("QGnomeTheme", "&OK"); + case QPlatformDialogHelper::Save: + return QCoreApplication::translate("QGnomeTheme", "&Save"); + case QPlatformDialogHelper::Cancel: + return QCoreApplication::translate("QGnomeTheme", "&Cancel"); + case QPlatformDialogHelper::Close: + return QCoreApplication::translate("QGnomeTheme", "&Close"); + case QPlatformDialogHelper::Discard: + return QCoreApplication::translate("QGnomeTheme", "Close without Saving"); + default: + break; + } + return QPlatformTheme::standardButtonText(button); +} + +/*! + \brief Creates a UNIX theme according to the detected desktop environment. +*/ + +QPlatformTheme *QGenericUnixTheme::createUnixTheme(const QString &name) +{ + if (name == QLatin1String(QGenericUnixTheme::name)) + return new QGenericUnixTheme; +#if QT_CONFIG(settings) + if (name == QLatin1String(QKdeTheme::name)) + if (QPlatformTheme *kdeTheme = QKdeTheme::createKdeTheme()) + return kdeTheme; +#endif + if (name == QLatin1String(QGnomeTheme::name)) + return new QGnomeTheme; + return nullptr; +} + +QStringList QGenericUnixTheme::themeNames() +{ + QStringList result; + if (QGuiApplication::desktopSettingsAware()) { + const QByteArray desktopEnvironment = QGuiApplicationPrivate::platformIntegration()->services()->desktopEnvironment(); + QList gtkBasedEnvironments; + gtkBasedEnvironments << "GNOME" + << "X-CINNAMON" + << "UNITY" + << "MATE" + << "XFCE" + << "LXDE"; + const QList desktopNames = desktopEnvironment.split(':'); + for (const QByteArray &desktopName : desktopNames) { + if (desktopEnvironment == "KDE") { +#if QT_CONFIG(settings) + result.push_back(QLatin1String(QKdeTheme::name)); +#endif + } else if (gtkBasedEnvironments.contains(desktopName)) { + // prefer the GTK3 theme implementation with native dialogs etc. + result.push_back(QStringLiteral("gtk3")); + // fallback to the generic Gnome theme if loading the GTK3 theme fails + result.push_back(QLatin1String(QGnomeTheme::name)); + } else { + // unknown, but lowercase the name (our standard practice) and + // remove any "x-" prefix + QString s = QString::fromLatin1(desktopName.toLower()); + result.push_back(s.startsWith(QLatin1String("x-")) ? s.mid(2) : s); + } + } + } // desktopSettingsAware + result.append(QLatin1String(QGenericUnixTheme::name)); + return result; +} + +QT_END_NAMESPACE diff --git a/src/gui/platform/unix/qgenericunixthemes_p.h b/src/gui/platform/unix/qgenericunixthemes_p.h new file mode 100644 index 0000000000..0870275888 --- /dev/null +++ b/src/gui/platform/unix/qgenericunixthemes_p.h @@ -0,0 +1,156 @@ +/**************************************************************************** +** +** Copyright (C) 2016 The Qt Company Ltd. +** Contact: https://www.qt.io/licensing/ +** +** This file is part of the plugins of the Qt Toolkit. +** +** $QT_BEGIN_LICENSE:LGPL$ +** Commercial License Usage +** Licensees holding valid commercial Qt licenses may use this file in +** accordance with the commercial license agreement provided with the +** Software or, alternatively, in accordance with the terms contained in +** a written agreement between you and The Qt Company. For licensing terms +** and conditions see https://www.qt.io/terms-conditions. For further +** information use the contact form at https://www.qt.io/contact-us. +** +** GNU Lesser General Public License Usage +** Alternatively, this file may be used under the terms of the GNU Lesser +** General Public License version 3 as published by the Free Software +** Foundation and appearing in the file LICENSE.LGPL3 included in the +** packaging of this file. Please review the following information to +** ensure the GNU Lesser General Public License version 3 requirements +** will be met: https://www.gnu.org/licenses/lgpl-3.0.html. +** +** GNU General Public License Usage +** Alternatively, this file may be used under the terms of the GNU +** General Public License version 2.0 or (at your option) the GNU General +** Public license version 3 or any later version approved by the KDE Free +** Qt Foundation. The licenses are as published by the Free Software +** Foundation and appearing in the file LICENSE.GPL2 and LICENSE.GPL3 +** included in the packaging of this file. Please review the following +** information to ensure the GNU General Public License requirements will +** be met: https://www.gnu.org/licenses/gpl-2.0.html and +** https://www.gnu.org/licenses/gpl-3.0.html. +** +** $QT_END_LICENSE$ +** +****************************************************************************/ + +#ifndef QGENERICUNIXTHEMES_H +#define QGENERICUNIXTHEMES_H + +// +// W A R N I N G +// ------------- +// +// This file is not part of the Qt API. It exists purely as an +// implementation detail. This header file may change from version to +// version without notice, or even be removed. +// +// We mean it. +// + +#include +#include +#include +#include + +QT_BEGIN_NAMESPACE + +class ResourceHelper +{ +public: + ResourceHelper(); + ~ResourceHelper() { clear(); } + + void clear(); + + QPalette *palettes[QPlatformTheme::NPalettes]; + QFont *fonts[QPlatformTheme::NFonts]; +}; + +class QGenericUnixThemePrivate; + +class Q_GUI_EXPORT QGenericUnixTheme : public QPlatformTheme +{ + Q_DECLARE_PRIVATE(QGenericUnixTheme) +public: + QGenericUnixTheme(); + + static QPlatformTheme *createUnixTheme(const QString &name); + static QStringList themeNames(); + + const QFont *font(Font type) const override; + QVariant themeHint(ThemeHint hint) const override; + + static QStringList xdgIconThemePaths(); + static QStringList iconFallbackPaths(); +#ifndef QT_NO_DBUS + QPlatformMenuBar *createPlatformMenuBar() const override; +#endif +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) + QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; +#endif + + static const char *name; +}; + +#if QT_CONFIG(settings) +class QKdeThemePrivate; + +class QKdeTheme : public QPlatformTheme +{ + Q_DECLARE_PRIVATE(QKdeTheme) +public: + QKdeTheme(const QStringList& kdeDirs, int kdeVersion); + + static QPlatformTheme *createKdeTheme(); + QVariant themeHint(ThemeHint hint) const override; + + QIcon fileIcon(const QFileInfo &fileInfo, + QPlatformTheme::IconOptions iconOptions = { }) const override; + + const QPalette *palette(Palette type = SystemPalette) const override; + + const QFont *font(Font type) const override; +#ifndef QT_NO_DBUS + QPlatformMenuBar *createPlatformMenuBar() const override; +#endif +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) + QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; +#endif + + static const char *name; +}; +#endif // settings + +class QGnomeThemePrivate; + +class Q_GUI_EXPORT QGnomeTheme : public QPlatformTheme +{ + Q_DECLARE_PRIVATE(QGnomeTheme) +public: + QGnomeTheme(); + QVariant themeHint(ThemeHint hint) const override; + QIcon fileIcon(const QFileInfo &fileInfo, + QPlatformTheme::IconOptions = { }) const override; + const QFont *font(Font type) const override; + QString standardButtonText(int button) const override; + + virtual QString gtkFontName() const; +#ifndef QT_NO_DBUS + QPlatformMenuBar *createPlatformMenuBar() const override; +#endif +#if !defined(QT_NO_DBUS) && !defined(QT_NO_SYSTEMTRAYICON) + QPlatformSystemTrayIcon *createPlatformSystemTrayIcon() const override; +#endif + + static const char *name; +}; + +QPlatformTheme *qt_createUnixTheme(); + +QT_END_NAMESPACE + +#endif // QGENERICUNIXTHEMES_H diff --git a/src/gui/platform/unix/unix.pri b/src/gui/platform/unix/unix.pri index b203e81b24..82eb63b11b 100644 --- a/src/gui/platform/unix/unix.pri +++ b/src/gui/platform/unix/unix.pri @@ -21,3 +21,15 @@ if(unix:!uikit)|qtConfig(xcb) { qtHaveModule(dbus): QT_PRIVATE += dbus } + +if(unix:!uikit:!macos)|qtConfig(xcb) { + SOURCES += \ + platform/unix/qgenericunixthemes.cpp + HEADERS += \ + platform/unix/qgenericunixthemes_p.h + + qtHaveModule(dbus) { + include(dbusmenu/dbusmenu.pri) + qtConfig(systemtrayicon): include(dbustray/dbustray.pri) + } +} -- cgit v1.2.3