diff --git a/applets/systemtray/package/contents/ui/ConfigEntries.qml b/applets/systemtray/package/contents/ui/ConfigEntries.qml index ca8b058ec..aefac2330 100644 --- a/applets/systemtray/package/contents/ui/ConfigEntries.qml +++ b/applets/systemtray/package/contents/ui/ConfigEntries.qml @@ -1,245 +1,245 @@ /* * Copyright 2013 Sebastian Kügler * Copyright 2014 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 2.010-1301, USA. */ import QtQuick 2.0 import QtQuick.Controls 1.0 as QtControls import QtQuick.Layouts 1.1 as QtLayouts import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents import org.kde.plasma.extras 2.0 as PlasmaExtras import org.kde.kquickcontrolsaddons 2.0 import org.kde.kquickcontrols 2.0 as KQC QtLayouts.GridLayout { id: iconsPage signal configurationChanged property var cfg_shownItems: [] property var cfg_hiddenItems: [] property alias cfg_showAllItems: showAllCheckBox.checked columns: 2 // so we can indent the entries below... function saveConfig () { for (var i in tableView.model) { //tableView.model[i].applet.globalShortcut = tableView.model[i].shortcut } } QtControls.CheckBox { id: showAllCheckBox QtLayouts.Layout.fillWidth: true QtLayouts.Layout.columnSpan: iconsPage.columns QtLayouts.Layout.row: 1 text: i18n("Always show all entries") } QtControls.Button { // just for measurement id: measureButton text: "measureButton" visible: false } // resizeToContents does not take into account the heading QtControls.Label { id: shortcutColumnMeasureLabel text: shortcutColumn.title visible: false } function retrieveAllItems() { print(plasmoid) print(plasmoid.rootItem.statusNotifierModel) var list = []; for (var i = 0; i < plasmoid.rootItem.statusNotifierModel.count; ++i) { var item = plasmoid.rootItem.statusNotifierModel.get(i); list.push({ "index": i, "taskId": item.Id, "name": item.Title, - "iconName": plasmoid.nativeInterface.resolveIcon(item.IconName, item.IconThemePath), + "iconName": item.IconName, "icon": item.Icon }); } var lastIndex = list.length; for (var i = 0; i < plasmoid.applets.length; ++i) { var item = plasmoid.applets[i] list.push({ "index": (i + lastIndex), "applet": item, "taskId": item.pluginName, "name": item.title, "iconName": item.icon, "shortcut": item.globalShortcut }); } return list; } QtControls.TableView { id: tableView QtLayouts.Layout.fillWidth: true QtLayouts.Layout.fillHeight: true QtLayouts.Layout.row: 2 QtLayouts.Layout.column: 1 model: retrieveAllItems() horizontalScrollBarPolicy: Qt.ScrollBarAlwaysOff flickableItem.boundsBehavior: Flickable.StopAtBounds Component.onCompleted: { visibilityColumn.resizeToContents() shortcutColumn.resizeToContents() } // Taken from QtQuickControls BasicTableViewStyle, just to make its height sensible... rowDelegate: BorderImage { visible: styleData.selected || styleData.alternate source: "image://__tablerow/" + (styleData.alternate ? "alternate_" : "") + (tableView.activeFocus ? "active" : "") height: measureButton.height border.left: 4 ; border.right: 4 } QtControls.TableViewColumn { id: entryColumn width: tableView.viewport.width - visibilityColumn.width - shortcutColumn.width title: i18nc("Name of the system tray entry", "Entry") movable: false resizable: false delegate: QtLayouts.RowLayout { Item { // spacer QtLayouts.Layout.preferredWidth: 1 QtLayouts.Layout.fillHeight: true } QIconItem { width: units.iconSizes.small height: width icon: modelData.iconName || modelData.icon || "" } QtControls.Label { QtLayouts.Layout.fillWidth: true text: modelData.name elide: Text.ElideRight wrapMode: Text.NoWrap } } } QtControls.TableViewColumn { id: visibilityColumn title: i18n("Visibility") movable: false resizable: false delegate: QtControls.ComboBox { implicitWidth: Math.round(units.gridUnit * 6.5) // ComboBox sizing is broken enabled: !showAllCheckBox.checked currentIndex: { if (cfg_shownItems.indexOf(modelData.taskId) != -1) { return 1; } else if (cfg_hiddenItems.indexOf(modelData.taskId) != -1) { return 2; } else { return 0; } } // activated, in contrast to currentIndexChanged, only fires if the user himself changed the value onActivated: { var shownIndex = cfg_shownItems.indexOf(modelData.taskId); var hiddenIndex = cfg_hiddenItems.indexOf(modelData.taskId); switch (index) { case 0: { if (shownIndex > -1) { cfg_shownItems.splice(shownIndex, 1); } if (hiddenIndex > -1) { cfg_hiddenItems.splice(hiddenIndex, 1); } break; } case 1: { if (shownIndex == -1) { cfg_shownItems.push(modelData.taskId); } if (hiddenIndex > -1) { cfg_hiddenItems.splice(hiddenIndex, 1); } break; } case 2: { if (shownIndex > -1) { cfg_shownItems.splice(shownIndex, 1); } if (hiddenIndex == -1) { cfg_hiddenItems.push(modelData.taskId); } break; } } iconsPage.configurationChanged(); } model: [i18n("Auto"), i18n("Shown"), i18n("Hidden")] } } QtControls.TableViewColumn { id: shortcutColumn title: i18n("Keyboard Shortcut") // FIXME doesn't fit movable: false resizable: false // this Item wrapper prevents TableView from ripping apart the two KeySequenceItem buttons delegate: Item { implicitWidth: Math.max(shortcutColumnMeasureLabel.width, keySequenceItem.width) + 10 height: keySequenceItem.height KQC.KeySequenceItem { id: keySequenceItem anchors.right: parent.right keySequence: modelData.shortcut // only Plasmoids have that visible: modelData.hasOwnProperty("shortcut") onKeySequenceChanged: { if (keySequence != modelData.shortcut) { // both SNIs and plasmoids are listed in the same TableView // but they come from two separate models, so we need to subtract // the SNI model count to get the actual plasmoid index var index = modelData.index - plasmoid.rootItem.statusNotifierModel.count plasmoid.applets[index].globalShortcut = keySequence iconsPage.configurationChanged() } shortcutColumn.resizeToContents() } } } } } } diff --git a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml index 115e1fbea..889b8b53d 100644 --- a/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml +++ b/applets/systemtray/package/contents/ui/items/StatusNotifierItem.qml @@ -1,116 +1,116 @@ /* * Copyright 2016 Marco Martin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU Library General Public License as * published by the Free Software Foundation; either version 2, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Library General Public License for more details * * You should have received a copy of the GNU Library General Public * License along with this program; if not, write to the * Free Software Foundation, Inc., * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ import QtQuick 2.1 import org.kde.plasma.core 2.0 as PlasmaCore import org.kde.plasma.components 2.0 as PlasmaComponents AbstractItem { id: taskIcon itemId: Id text: Title mainText: ToolTipTitle != "" ? ToolTipTitle : Title subText: ToolTipSubTitle - icon: ToolTipIcon != "" ? ToolTipIcon : plasmoid.nativeInterface.resolveIcon(IconName != "" ? IconName : Icon, IconThemePath) + icon: ToolTipIcon != "" ? ToolTipIcon : Icon ? Icon : IconName textFormat: Text.AutoText category: Category status: { switch (Status) { case "Active": return PlasmaCore.Types.ActiveStatus; case "NeedsAttention": return PlasmaCore.Types.NeedsAttentionStatus; //just assume passive default: return PlasmaCore.Types.PassiveStatus; } } iconItem: iconItem PlasmaCore.IconItem { id: iconItem - source: plasmoid.nativeInterface.resolveIcon(IconName != "" ? IconName : Icon, IconThemePath) + source: Icon ? Icon : IconName width: Math.min(parent.width, parent.height) height: width active: taskIcon.containsMouse anchors { left: parent.left verticalCenter: parent.verticalCenter } } onClicked: { var pos = plasmoid.nativeInterface.popupPosition(taskIcon, 0, 0); switch (mouse.button) { case Qt.LeftButton: { var service = statusNotifierSource.serviceForSource(DataEngineSource); var operation = service.operationDescription("Activate"); operation.x = pos.x; operation.y = pos.y; service.startOperationCall(operation); break; } case Qt.RightButton: { var service = statusNotifierSource.serviceForSource(DataEngineSource); var operation = service.operationDescription("ContextMenu"); operation.x = pos.x; operation.y = pos.y; var job = service.startOperationCall(operation); job.finished.connect(function () { plasmoid.nativeInterface.showStatusNotifierContextMenu(job, taskIcon); }); break; } case Qt.MiddleButton: var service = statusNotifierSource.serviceForSource(DataEngineSource); var operation = service.operationDescription("SecondaryActivate"); operation.x = pos.x; operation.y = pos.y; service.startOperationCall(operation); break; break; } } onWheel: { //don't send activateVertScroll with a delta of 0, some clients seem to break (kmix) if (wheel.angleDelta.y !== 0) { var service = statusNotifierSource.serviceForSource(DataEngineSource); var operation = service.operationDescription("Scroll"); operation.delta =wheel.angleDelta.y; operation.direction = "Vertical"; service.startOperationCall(operation); } if (wheel.angleDelta.x !== 0) { var service = statusNotifierSource.serviceForSource(DataEngineSource); var operation = service.operationDescription("Scroll"); operation.delta =wheel.angleDelta.x; operation.direction = "Horizontal"; service.startOperationCall(operation); } } } diff --git a/applets/systemtray/systemtray.cpp b/applets/systemtray/systemtray.cpp index e1cd610c4..b9c88a2ba 100644 --- a/applets/systemtray/systemtray.cpp +++ b/applets/systemtray/systemtray.cpp @@ -1,604 +1,552 @@ /*************************************************************************** * Copyright (C) 2015 Marco Martin * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "systemtray.h" #include "debug.h" #include #include #include #include #include #include #include #include #include #include #include #include #include -#include -#include #include #include #include -/* - * An app may also load icons from their own directories, so we need a new iconloader that takes this into account - * This is wrapped into a subclass of iconengine so the iconloader lifespan matches the icon object - */ -class AppIconEngine : public KIconEngine -{ -public: - AppIconEngine(const QString &variant, const QString &path, const QString &appName); - ~AppIconEngine(); -private: - KIconLoader* m_loader; -}; - -AppIconEngine::AppIconEngine(const QString &variant, const QString &path, const QString &appName) : - KIconEngine(variant, m_loader = new KIconLoader(appName, QStringList())) -{ - m_loader->addAppDir(appName, path); -} - -AppIconEngine::~AppIconEngine() -{ - delete m_loader; -} - class PlasmoidModel: public QStandardItemModel { public: PlasmoidModel(QObject *parent = 0) : QStandardItemModel(parent) { } QHash roleNames() const override { QHash roles = QStandardItemModel::roleNames(); roles[Qt::UserRole+1] = "plugin"; return roles; } }; SystemTray::SystemTray(QObject *parent, const QVariantList &args) : Plasma::Containment(parent, args), m_availablePlasmoidsModel(nullptr) { setHasConfigurationInterface(true); setContainmentType(Plasma::Types::CustomEmbeddedContainment); } SystemTray::~SystemTray() { } void SystemTray::init() { Containment::init(); for (const auto &info: Plasma::PluginLoader::self()->listAppletInfo(QString())) { if (!info.isValid() || info.property(QStringLiteral("X-Plasma-NotificationArea")) != "true") { continue; } m_systrayApplets[info.pluginName()] = info; if (info.isPluginEnabledByDefault()) { m_defaultPlasmoids += info.pluginName(); } const QString dbusactivation = info.property(QStringLiteral("X-Plasma-DBusActivationService")).toString(); if (!dbusactivation.isEmpty()) { qCDebug(SYSTEM_TRAY) << "ST Found DBus-able Applet: " << info.pluginName() << dbusactivation; m_dbusActivatableTasks[info.pluginName()] = dbusactivation; } } } void SystemTray::newTask(const QString &task) { foreach (Plasma::Applet *applet, applets()) { if (!applet->pluginInfo().isValid()) { continue; } //only allow one instance per applet if (task == applet->pluginInfo().pluginName()) { //Applet::destroy doesn't delete the applet from Containment::applets in the same event //potentially a dbus activated service being restarted can be added in this time. if (!applet->destroyed()) { return; } } } //known one, recycle the id to reuse old config if (m_knownPlugins.contains(task)) { Applet *applet = Plasma::PluginLoader::self()->loadApplet(task, m_knownPlugins.value(task), QVariantList()); //this should never happen unless explicitly wrong config is hand-written or //(more likely) a previously added applet is uninstalled if (!applet) { qWarning() << "Unable to find applet" << task; return; } applet->setProperty("org.kde.plasma:force-create", true); addApplet(applet); //create a new one automatic id, new config group } else { Applet * applet = createApplet(task, QVariantList() << "org.kde.plasma:force-create"); if (applet) { m_knownPlugins[task] = applet->id(); } } } void SystemTray::cleanupTask(const QString &task) { foreach (Plasma::Applet *applet, applets()) { if (!applet->pluginInfo().isValid() || task == applet->pluginInfo().pluginName()) { //we are *not* cleaning the config here, because since is one //of those automatically loaded/unloaded by dbus, we want to recycle //the config the next time it's loaded, in case the user configured something here applet->deleteLater(); //HACK: we need to remove the applet from Containment::applets() as soon as possible //otherwise we may have disappearing applets for restarting dbus services //this may be removed when we depend from a frameworks version in which appletDeleted is emitted as soon as deleteLater() is called emit appletDeleted(applet); } } } -QVariant SystemTray::resolveIcon(const QVariant &variant, const QString &iconThemePath) -{ - if (variant.canConvert()) { - if (!iconThemePath.isEmpty()) { - const QString path = iconThemePath; - if (!path.isEmpty()) { - // FIXME: If last part of path is not "icons", this won't work! - auto tokens = path.splitRef('/', QString::SkipEmptyParts); - if (tokens.length() >= 3 && tokens.takeLast() == QLatin1String("icons")) { - const QString appName = tokens.takeLast().toString(); - - return QVariant(QIcon(new AppIconEngine(variant.toString(), path, appName))); - } else { - qCWarning(SYSTEM_TRAY) << "Wrong IconThemePath" << path << ": too short or does not end with 'icons'"; - } - } - - //return just the string hoping that IconItem will know how to interpret it anyways as either a normal icon or a SVG from the theme - return variant; - } - } - - // Most importantly QIcons. Nothing to do for those. - return variant; -} - void SystemTray::showPlasmoidMenu(QQuickItem *appletInterface, int x, int y) { if (!appletInterface) { return; } Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); QPointF pos = appletInterface->mapToScene(QPointF(x, y)); if (appletInterface->window() && appletInterface->window()->screen()) { pos = appletInterface->window()->mapToGlobal(pos.toPoint()); } else { pos = QPoint(); } QMenu *desktopMenu = new QMenu; connect(this, &QObject::destroyed, desktopMenu, &QMenu::close); desktopMenu->setAttribute(Qt::WA_DeleteOnClose); emit applet->contextualActionsAboutToShow(); foreach (QAction *action, applet->contextualActions()) { if (action) { desktopMenu->addAction(action); } } QAction *runAssociatedApplication = applet->actions()->action(QStringLiteral("run associated application")); if (runAssociatedApplication && runAssociatedApplication->isEnabled()) { desktopMenu->addAction(runAssociatedApplication); } if (applet->actions()->action(QStringLiteral("configure"))) { desktopMenu->addAction(applet->actions()->action(QStringLiteral("configure"))); } if (desktopMenu->isEmpty()) { delete desktopMenu; return; } desktopMenu->adjustSize(); if (QScreen *screen = appletInterface->window()->screen()) { const QRect geo = screen->availableGeometry(); pos = QPoint(qBound(geo.left(), (int)pos.x(), geo.right() - desktopMenu->width()), qBound(geo.top(), (int)pos.y(), geo.bottom() - desktopMenu->height())); } desktopMenu->popup(pos.toPoint()); } Q_INVOKABLE QString SystemTray::plasmoidCategory(QQuickItem *appletInterface) const { if (!appletInterface) { return "UnknownCategory"; } Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); if (!applet || !applet->pluginInfo().isValid()) { return "UnknownCategory"; } const QString cat = applet->pluginInfo().property(QStringLiteral("X-Plasma-NotificationAreaCategory")).toString(); if (cat.isEmpty()) { return "UnknownCategory"; } return cat; } void SystemTray::showStatusNotifierContextMenu(KJob *job, QQuickItem *statusNotifierIcon) { if (QCoreApplication::closingDown() || !statusNotifierIcon) { // apparently an edge case can be triggered due to the async nature of all this // see: https://bugs.kde.org/show_bug.cgi?id=251977 return; } Plasma::ServiceJob *sjob = qobject_cast(job); if (!sjob) { return; } QMenu *menu = qobject_cast(sjob->result().value()); if (menu) { menu->adjustSize(); const auto parameters = sjob->parameters(); int x = parameters[QStringLiteral("x")].toInt(); int y = parameters[QStringLiteral("y")].toInt(); //try tofind the icon screen coordinates, and adjust the position as a poor //man's popupPosition QRect screenItemRect(statusNotifierIcon->mapToScene(QPointF(0, 0)).toPoint(), QSize(statusNotifierIcon->width(), statusNotifierIcon->height())); if (statusNotifierIcon->window()) { screenItemRect.moveTopLeft(statusNotifierIcon->window()->mapToGlobal(screenItemRect.topLeft())); } switch (location()) { case Plasma::Types::LeftEdge: x = screenItemRect.right(); y = screenItemRect.top(); break; case Plasma::Types::RightEdge: x = screenItemRect.left() - menu->width(); y = screenItemRect.top(); break; case Plasma::Types::TopEdge: x = screenItemRect.left(); y = screenItemRect.bottom(); break; case Plasma::Types::BottomEdge: x = screenItemRect.left(); y = screenItemRect.top() - menu->height(); break; default: x = screenItemRect.left(); if (screenItemRect.top() - menu->height() >= statusNotifierIcon->window()->screen()->geometry().top()) { y = screenItemRect.top() - menu->height(); } else { y = screenItemRect.bottom(); } } menu->popup(QPoint(x, y)); } } QPointF SystemTray::popupPosition(QQuickItem* visualParent, int x, int y) { if (!visualParent) { return QPointF(0, 0); } QPointF pos = visualParent->mapToScene(QPointF(x, y)); if (visualParent->window() && visualParent->window()->screen()) { pos = visualParent->window()->mapToGlobal(pos.toPoint()); } else { return QPoint(); } return pos; } void SystemTray::reorderItemBefore(QQuickItem* before, QQuickItem* after) { if (!before || !after) { return; } before->setVisible(false); before->setParentItem(after->parentItem()); before->stackBefore(after); before->setVisible(true); } void SystemTray::reorderItemAfter(QQuickItem* after, QQuickItem* before) { if (!before || !after) { return; } after->setVisible(false); after->setParentItem(before->parentItem()); after->stackAfter(before); after->setVisible(true); } void SystemTray::restoreContents(KConfigGroup &group) { Q_UNUSED(group); //NOTE: RestoreContents shouldnn't do anything here because is too soon, so have an empty reimplementation } void SystemTray::restorePlasmoids() { if (!isContainment()) { qCWarning(SYSTEM_TRAY) << "Loaded as an applet, this shouldn't have happened"; return; } //First: remove all that are not allowed anymore foreach (Plasma::Applet *applet, applets()) { //Here it should always be valid. //for some reason it not always is. if (!applet->pluginInfo().isValid()) { applet->config().parent().deleteGroup(); applet->deleteLater(); } else { const QString task = applet->pluginInfo().pluginName(); if (!m_allowedPlasmoids.contains(task)) { //in those cases we do delete the applet config completely //as they were explicitly disabled by the user applet->config().parent().deleteGroup(); applet->deleteLater(); } } } KConfigGroup cg = config(); cg = KConfigGroup(&cg, "Applets"); foreach (const QString &group, cg.groupList()) { KConfigGroup appletConfig(&cg, group); QString plugin = appletConfig.readEntry("plugin"); if (!plugin.isEmpty()) { m_knownPlugins[plugin] = group.toInt(); } } QStringList ownApplets; QMap sortedApplets; for (auto it = m_systrayApplets.constBegin(); it != m_systrayApplets.constEnd(); ++it) { const KPluginInfo &info = it.value(); if (m_allowedPlasmoids.contains(info.pluginName()) && ! m_dbusActivatableTasks.contains(info.pluginName())) { //FIXME // if we already have a plugin with this exact name in it, then check if it is the // same plugin and skip it if it is indeed already listed if (sortedApplets.contains(info.name())) { bool dupe = false; // it is possible (though poor form) to have multiple applets // with the same visible name but different plugins, so we hve to check all values foreach (const KPluginInfo &existingInfo, sortedApplets.values(info.name())) { if (existingInfo.pluginName() == info.pluginName()) { dupe = true; break; } } if (dupe) { continue; } } // insertMulti becase it is possible (though poor form) to have multiple applets // with the same visible name but different plugins sortedApplets.insertMulti(info.name(), info); } } foreach (const KPluginInfo &info, sortedApplets) { qCDebug(SYSTEM_TRAY) << " Adding applet: " << info.name(); if (m_allowedPlasmoids.contains(info.pluginName())) { newTask(info.pluginName()); } } initDBusActivatables(); } QStringList SystemTray::defaultPlasmoids() const { return m_defaultPlasmoids; } QAbstractItemModel* SystemTray::availablePlasmoids() { if (!m_availablePlasmoidsModel) { m_availablePlasmoidsModel = new PlasmoidModel(this); foreach (const KPluginInfo &info, m_systrayApplets) { QString name = info.name(); const QString dbusactivation = info.property(QStringLiteral("X-Plasma-DBusActivationService")).toString(); if (!dbusactivation.isEmpty()) { name += i18n(" (Automatic load)"); } QStandardItem *item = new QStandardItem(QIcon::fromTheme(info.icon()), name); item->setData(info.pluginName()); m_availablePlasmoidsModel->appendRow(item); } } return m_availablePlasmoidsModel; } QStringList SystemTray::allowedPlasmoids() const { return m_allowedPlasmoids; } void SystemTray::setAllowedPlasmoids(const QStringList &allowed) { if (allowed == m_allowedPlasmoids) { return; } m_allowedPlasmoids = allowed; restorePlasmoids(); emit allowedPlasmoidsChanged(); } void SystemTray::initDBusActivatables() { /* Loading and unloading Plasmoids when dbus services come and go * * This works as follows: * - we collect a list of plugins and related services in m_dbusActivatableTasks * - we query DBus for the list of services, async (initDBusActivatables()) * - we go over that list, adding tasks when a service and plugin match (serviceNameFetchFinished()) * - we start watching for new services, and do the same (serviceNameFetchFinished()) * - whenever a service is gone, we check whether to unload a Plasmoid (serviceUnregistered()) */ QDBusPendingCall async = QDBusConnection::sessionBus().interface()->asyncCall(QStringLiteral("ListNames")); QDBusPendingCallWatcher *callWatcher = new QDBusPendingCallWatcher(async, this); connect(callWatcher, &QDBusPendingCallWatcher::finished, [=](QDBusPendingCallWatcher *callWatcher){ SystemTray::serviceNameFetchFinished(callWatcher, QDBusConnection::sessionBus()); }); QDBusPendingCall systemAsync = QDBusConnection::systemBus().interface()->asyncCall(QStringLiteral("ListNames")); QDBusPendingCallWatcher *systemCallWatcher = new QDBusPendingCallWatcher(systemAsync, this); connect(systemCallWatcher, &QDBusPendingCallWatcher::finished, [=](QDBusPendingCallWatcher *callWatcher){ SystemTray::serviceNameFetchFinished(callWatcher, QDBusConnection::systemBus()); }); } void SystemTray::serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection) { QDBusPendingReply propsReply = *watcher; watcher->deleteLater(); if (propsReply.isError()) { qCWarning(SYSTEM_TRAY) << "Could not get list of available D-Bus services"; } else { foreach (const QString& serviceName, propsReply.value()) { serviceRegistered(serviceName); } } // Watch for new services // We need to watch for all of new services here, since we want to "match" the names, // not just compare them // This makes mpris work, since it wants to match org.mpris.MediaPlayer2.dragonplayer // against org.mpris.MediaPlayer2 // QDBusServiceWatcher is not capable for watching wildcard service right now // See: // https://bugreports.qt.io/browse/QTBUG-51683 // https://bugreports.qt.io/browse/QTBUG-33829 connect(connection.interface(), &QDBusConnectionInterface::serviceOwnerChanged, this, &SystemTray::serviceOwnerChanged); } void SystemTray::serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner) { if (oldOwner.isEmpty()) { serviceRegistered(serviceName); } else if (newOwner.isEmpty()) { serviceUnregistered(serviceName); } } void SystemTray::serviceRegistered(const QString &service) { //qCDebug(SYSTEM_TRAY) << "DBus service appeared:" << service; for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) { const QString &plugin = it.key(); if (!m_allowedPlasmoids.contains(plugin)) { continue; } const QString &pattern = it.value(); QRegExp rx(pattern); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(service)) { //qCDebug(SYSTEM_TRAY) << "ST : DBus service " << m_dbusActivatableTasks[plugin] << "appeared. Loading " << plugin; newTask(plugin); m_dbusServiceCounts[plugin]++; } } } void SystemTray::serviceUnregistered(const QString &service) { //qCDebug(SYSTEM_TRAY) << "DBus service disappeared:" << service; for (auto it = m_dbusActivatableTasks.constBegin(), end = m_dbusActivatableTasks.constEnd(); it != end; ++it) { const QString &plugin = it.key(); if (!m_allowedPlasmoids.contains(plugin)) { continue; } const QString &pattern = it.value(); QRegExp rx(pattern); rx.setPatternSyntax(QRegExp::Wildcard); if (rx.exactMatch(service)) { m_dbusServiceCounts[plugin]--; Q_ASSERT(m_dbusServiceCounts[plugin] >= 0); if (m_dbusServiceCounts[plugin] == 0) { //qCDebug(SYSTEM_TRAY) << "ST : DBus service " << m_dbusActivatableTasks[plugin] << " disappeared. Unloading " << plugin; cleanupTask(plugin); } } } } K_EXPORT_PLASMA_APPLET_WITH_JSON(systemtray, SystemTray, "metadata.json") #include "systemtray.moc" diff --git a/applets/systemtray/systemtray.h b/applets/systemtray/systemtray.h index 953d81b41..0a93fe2bc 100644 --- a/applets/systemtray/systemtray.h +++ b/applets/systemtray/systemtray.h @@ -1,129 +1,123 @@ /*************************************************************************** * Copyright (C) 2015 Marco Martin * * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #ifndef SYSTEMTRAY_H #define SYSTEMTRAY_H #include #include class QDBusPendingCallWatcher; class QDBusConnection; class QQuickItem; class PlasmoidModel; class SystemTray : public Plasma::Containment { Q_OBJECT Q_PROPERTY(QAbstractItemModel* availablePlasmoids READ availablePlasmoids CONSTANT) Q_PROPERTY(QStringList allowedPlasmoids READ allowedPlasmoids WRITE setAllowedPlasmoids NOTIFY allowedPlasmoidsChanged) Q_PROPERTY(QStringList defaultPlasmoids READ defaultPlasmoids CONSTANT) public: SystemTray( QObject *parent, const QVariantList &args ); ~SystemTray() override; void init() override; void restoreContents(KConfigGroup &group) Q_DECL_OVERRIDE; void restorePlasmoids(); QStringList defaultPlasmoids() const; QAbstractItemModel* availablePlasmoids(); QStringList allowedPlasmoids() const; void setAllowedPlasmoids(const QStringList &allowed); //Creates an applet *if not already existing* void newTask(const QString &task); //cleans all instances of a given applet void cleanupTask(const QString &task); //Invokable utilities - /** - * returns either a simple icon name or a custom path if the app is - * using a custom theme - */ - Q_INVOKABLE QVariant resolveIcon(const QVariant &variant, const QString &iconThemePath); - /** * Given an AppletInterface pointer, shows a proper context menu for it */ Q_INVOKABLE void showPlasmoidMenu(QQuickItem *appletInterface, int x, int y); /** * Returns the "X-Plasma-NotificationAreaCategory" * of the plasmoid metadata */ Q_INVOKABLE QString plasmoidCategory(QQuickItem *appletInterface) const; /** * Shows the context menu for a statusnotifieritem */ Q_INVOKABLE void showStatusNotifierContextMenu(KJob *job, QQuickItem *statusNotifierIcon); /** * Find out global coordinates for a popup given local MouseArea * coordinates */ Q_INVOKABLE QPointF popupPosition(QQuickItem* visualParent, int x, int y); /** * Reparent the item "before" with the same parent as the item "after", * then restack it before it, using QQuickITem::stackBefore. * used to quickly reorder icons in the systray (or hidden popup) * @see QQuickITem::stackBefore */ Q_INVOKABLE void reorderItemBefore(QQuickItem* before, QQuickItem* after); /** * Reparent the item "after" with the same parent as the item "before", * then restack it after it, using QQuickITem::stackAfter. * used to quickly reorder icons in the systray (or hidden popup) * @see QQuickITem::stackAfter */ Q_INVOKABLE void reorderItemAfter(QQuickItem* after, QQuickItem* before); private Q_SLOTS: void serviceNameFetchFinished(QDBusPendingCallWatcher* watcher, const QDBusConnection &connection); void serviceOwnerChanged(const QString &serviceName, const QString &oldOwner, const QString &newOwner); private: void serviceRegistered(const QString &service); void serviceUnregistered(const QString &service); Q_SIGNALS: void allowedPlasmoidsChanged(); private: void initDBusActivatables(); QStringList m_defaultPlasmoids; QHash m_systrayApplets; QHash m_dbusActivatableTasks; QStringList m_allowedPlasmoids; PlasmoidModel *m_availablePlasmoidsModel; QHash m_knownPlugins; QHash m_dbusServiceCounts; }; #endif diff --git a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp index 2df5e795a..d835d50c4 100644 --- a/dataengines/statusnotifieritem/statusnotifieritemsource.cpp +++ b/dataengines/statusnotifieritem/statusnotifieritemsource.cpp @@ -1,489 +1,494 @@ /*************************************************************************** * * * Copyright (C) 2009 Marco Martin * * Copyright (C) 2009 Matthieu Gallien * * * * This program is free software; you can redistribute it and/or modify * * it under the terms of the GNU General Public License as published by * * the Free Software Foundation; either version 2 of the License, or * * (at your option) any later version. * * * * This program is distributed in the hope that it will be useful, * * but WITHOUT ANY WARRANTY; without even the implied warranty of * * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * * GNU General Public License for more details. * * * * You should have received a copy of the GNU General Public License * * along with this program; if not, write to the * * Free Software Foundation, Inc., * * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA . * ***************************************************************************/ #include "statusnotifieritemsource.h" #include "systemtraytypes.h" #include "statusnotifieritemservice.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include class PlasmaDBusMenuImporter : public DBusMenuImporter { public: PlasmaDBusMenuImporter(const QString &service, const QString &path, KIconLoader *iconLoader, QObject *parent) : DBusMenuImporter(service, path, parent) , m_iconLoader(iconLoader) {} protected: QIcon iconForName(const QString &name) override { return QIcon(new KIconEngine(name, m_iconLoader)); } private: KIconLoader *m_iconLoader; }; StatusNotifierItemSource::StatusNotifierItemSource(const QString ¬ifierItemId, QObject *parent) : Plasma::DataContainer(parent), m_customIconLoader(0), m_menuImporter(0), m_refreshing(false), m_needsReRefreshing(false), m_titleUpdate(true), m_iconUpdate(true), m_tooltipUpdate(true), m_statusUpdate(true) { setObjectName(notifierItemId); qDBusRegisterMetaType(); qDBusRegisterMetaType(); qDBusRegisterMetaType(); m_typeId = notifierItemId; m_name = notifierItemId; //set the initial values for all the things //this is important as Plasma::DataModel has an unsolvable bug //when it gets data with a new key it tries to update the QAIM roleNames //from QML this achieves absolutely nothing as there is no signal to tell QQmlDelegateModel to reload the roleNames in QQmlAdapatorModel //no matter if the row changes or the model refreshes //this means it does not re-evaluate what bindings exist (watchedRoleIds) - and we get properties that don't bind and thus system tray icons //by setting everything up-front so that we have all role names when we call the first checkForUpdate() setData(QStringLiteral("AttentionIcon"), QIcon()); setData(QStringLiteral("AttentionIconName"), QString()); setData(QStringLiteral("AttentionMovieName"), QString()); setData(QStringLiteral("Category"), QString()); setData(QStringLiteral("Icon"), QIcon()); setData(QStringLiteral("IconName"), QString()); setData(QStringLiteral("IconsChanged"), false); setData(QStringLiteral("IconThemePath"), QString()); setData(QStringLiteral("Id"), QString()); setData(QStringLiteral("ItemIsMenu"), false); setData(QStringLiteral("OverlayIconName"), QString()); setData(QStringLiteral("StatusChanged"), false); setData(QStringLiteral("Status"), QString()); setData(QStringLiteral("TitleChanged"), false); setData(QStringLiteral("Title"), QString()); setData(QStringLiteral("ToolTipChanged"), false); setData(QStringLiteral("ToolTipIcon"), QString()); setData(QStringLiteral("ToolTipSubTitle"), QString()); setData(QStringLiteral("ToolTipTitle"), QString()); setData(QStringLiteral("WindowId"), QVariant()); int slash = notifierItemId.indexOf('/'); if (slash == -1) { qWarning() << "Invalid notifierItemId:" << notifierItemId; m_valid = false; m_statusNotifierItemInterface = 0; return; } QString service = notifierItemId.left(slash); QString path = notifierItemId.mid(slash); m_statusNotifierItemInterface = new org::kde::StatusNotifierItem(service, path, QDBusConnection::sessionBus(), this); m_refreshTimer.setSingleShot(true); m_refreshTimer.setInterval(10); connect(&m_refreshTimer, &QTimer::timeout, this, &StatusNotifierItemSource::performRefresh); m_valid = !service.isEmpty() && m_statusNotifierItemInterface->isValid(); if (m_valid) { connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewTitle, this, &StatusNotifierItemSource::refreshTitle); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewAttentionIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewOverlayIcon, this, &StatusNotifierItemSource::refreshIcons); connect(m_statusNotifierItemInterface, &OrgKdeStatusNotifierItem::NewToolTip, this, &StatusNotifierItemSource::refreshToolTip); connect(m_statusNotifierItemInterface, SIGNAL(NewStatus(QString)), this, SLOT(syncStatus(QString))); refresh(); } } StatusNotifierItemSource::~StatusNotifierItemSource() { delete m_statusNotifierItemInterface; } KIconLoader *StatusNotifierItemSource::iconLoader() const { return m_customIconLoader ? m_customIconLoader : KIconLoader::global(); } Plasma::Service *StatusNotifierItemSource::createService() { return new StatusNotifierItemService(this); } void StatusNotifierItemSource::syncStatus(QString status) { setData(QStringLiteral("TitleChanged"), false); setData(QStringLiteral("IconsChanged"), false); setData(QStringLiteral("TooltipChanged"), false); setData(QStringLiteral("StatusChanged"), true); setData(QStringLiteral("Status"), status); checkForUpdate(); } void StatusNotifierItemSource::refreshTitle() { m_titleUpdate = true; refresh(); } void StatusNotifierItemSource::refreshIcons() { m_iconUpdate = true; refresh(); } void StatusNotifierItemSource::refreshToolTip() { m_tooltipUpdate = true; refresh(); } void StatusNotifierItemSource::refresh() { if (!m_refreshTimer.isActive()) { m_refreshTimer.start(); } } void StatusNotifierItemSource::performRefresh() { if (m_refreshing) { m_needsReRefreshing = true; return; } m_refreshing = true; QDBusMessage message = QDBusMessage::createMethodCall(m_statusNotifierItemInterface->service(), m_statusNotifierItemInterface->path(), QStringLiteral("org.freedesktop.DBus.Properties"), QStringLiteral("GetAll")); message << m_statusNotifierItemInterface->interface(); QDBusPendingCall call = m_statusNotifierItemInterface->connection().asyncCall(message); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, &StatusNotifierItemSource::refreshCallback); } /** \todo add a smart pointer to guard call and to automatically delete it at the end of the function */ void StatusNotifierItemSource::refreshCallback(QDBusPendingCallWatcher *call) { m_refreshing = false; if (m_needsReRefreshing) { m_needsReRefreshing = false; performRefresh(); call->deleteLater(); return; } QDBusPendingReply reply = *call; if (reply.isError()) { m_valid = false; } else { // record what has changed setData(QStringLiteral("TitleChanged"), m_titleUpdate); m_titleUpdate = false; setData(QStringLiteral("IconsChanged"), m_iconUpdate); m_iconUpdate = false; setData(QStringLiteral("ToolTipChanged"), m_tooltipUpdate); m_tooltipUpdate = false; setData(QStringLiteral("StatusChanged"), m_statusUpdate); m_statusUpdate = false; //IconThemePath (handle this one first, because it has an impact on //others) QVariantMap properties = reply.argumentAt<0>(); QString path = properties[QStringLiteral("IconThemePath")].toString(); if (!path.isEmpty() && path != data()[QStringLiteral("IconThemePath")].toString()) { if (!m_customIconLoader) { m_customIconLoader = new KIconLoader(QString(), QStringList(), this); } + // FIXME: If last part of path is not "icons", this won't work! + QString appName; + auto tokens = path.splitRef('/', QString::SkipEmptyParts); + if (tokens.length() >= 3 && tokens.takeLast() == QLatin1String("icons")) + appName = tokens.takeLast().toString(); //icons may be either in the root directory of the passed path or in a appdir format //i.e hicolor/32x32/iconname.png - m_customIconLoader->reconfigure(QString(), QStringList(path)); + m_customIconLoader->reconfigure(appName, QStringList(path)); //add app dir requires an app name, though this is completely unused in this context - m_customIconLoader->addAppDir(QStringLiteral("unused"), path); + m_customIconLoader->addAppDir(appName.size() ? appName : QStringLiteral("unused"), path); } setData(QStringLiteral("IconThemePath"), path); setData(QStringLiteral("Category"), properties[QStringLiteral("Category")]); setData(QStringLiteral("Status"), properties[QStringLiteral("Status")]); setData(QStringLiteral("Title"), properties[QStringLiteral("Title")]); setData(QStringLiteral("Id"), properties[QStringLiteral("Id")]); setData(QStringLiteral("WindowId"), properties[QStringLiteral("WindowId")]); setData(QStringLiteral("ItemIsMenu"), properties[QStringLiteral("ItemIsMenu")]); //Attention Movie setData(QStringLiteral("AttentionMovieName"), properties[QStringLiteral("AttentionMovieName")]); QIcon overlay; QStringList overlayNames; //Icon { KDbusImageVector image; QIcon icon; QString iconName; properties[QStringLiteral("OverlayIconPixmap")].value() >> image; if (image.isEmpty()) { QString iconName = properties[QStringLiteral("OverlayIconName")].toString(); setData(QStringLiteral("OverlayIconName"), iconName); if (!iconName.isEmpty()) { overlayNames << iconName; overlay = QIcon(new KIconEngine(iconName, iconLoader())); } } else { overlay = imageVectorToPixmap(image); } properties[QStringLiteral("IconPixmap")].value() >> image; if (image.isEmpty()) { iconName = properties[QStringLiteral("IconName")].toString(); if (!iconName.isEmpty()) { icon = QIcon(new KIconEngine(iconName, iconLoader())); if (overlayNames.isEmpty() && !overlay.isNull()) { overlayIcon(&icon, &overlay); } } } else { icon = imageVectorToPixmap(image); if (!icon.isNull() && !overlay.isNull()) { overlayIcon(&icon, &overlay); } } setData(QStringLiteral("Icon"), icon); setData(QStringLiteral("IconName"), iconName); } //Attention icon { KDbusImageVector image; QIcon attentionIcon; properties[QStringLiteral("AttentionIconPixmap")].value() >> image; if (image.isEmpty()) { QString iconName = properties[QStringLiteral("AttentionIconName")].toString(); setData(QStringLiteral("AttentionIconName"), iconName); if (!iconName.isEmpty()) { attentionIcon = QIcon(new KIconEngine(iconName, iconLoader())); if (overlayNames.isEmpty() && !overlay.isNull()) { overlayIcon(&attentionIcon, &overlay); } } } else { attentionIcon = imageVectorToPixmap(image); if (!attentionIcon.isNull() && !overlay.isNull()) { overlayIcon(&attentionIcon, &overlay); } } setData(QStringLiteral("AttentionIcon"), attentionIcon); } //ToolTip { KDbusToolTipStruct toolTip; properties[QStringLiteral("ToolTip")].value() >> toolTip; if (toolTip.title.isEmpty()) { setData(QStringLiteral("ToolTipTitle"), QString()); setData(QStringLiteral("ToolTipSubTitle"), QString()); setData(QStringLiteral("ToolTipIcon"), QString()); } else { QIcon toolTipIcon; if (toolTip.image.size() == 0) { toolTipIcon = QIcon(new KIconEngine(toolTip.icon, iconLoader())); } else { toolTipIcon = imageVectorToPixmap(toolTip.image); } setData(QStringLiteral("ToolTipTitle"), toolTip.title); setData(QStringLiteral("ToolTipSubTitle"), toolTip.subTitle); if (toolTipIcon.isNull() || toolTipIcon.availableSizes().isEmpty()) { setData(QStringLiteral("ToolTipIcon"), QString()); } else { setData(QStringLiteral("ToolTipIcon"), toolTipIcon); } } } //Menu if (!m_menuImporter) { QString menuObjectPath = properties[QStringLiteral("Menu")].value().path(); if (!menuObjectPath.isEmpty()) { if (menuObjectPath == QLatin1String("/NO_DBUSMENU")) { // This is a hack to make it possible to disable DBusMenu in an // application. The string "/NO_DBUSMENU" must be the same as in // KStatusNotifierItem::setContextMenu(). qWarning() << "DBusMenu disabled for this application"; } else { m_menuImporter = new PlasmaDBusMenuImporter(m_statusNotifierItemInterface->service(), menuObjectPath, iconLoader(), this); connect(m_menuImporter, SIGNAL(menuUpdated()), this, SLOT(contextMenuReady())); } } } } checkForUpdate(); call->deleteLater(); } void StatusNotifierItemSource::contextMenuReady() { emit contextMenuReady(m_menuImporter->menu()); } QPixmap StatusNotifierItemSource::KDbusImageStructToPixmap(const KDbusImageStruct &image) const { //swap from network byte order if we are little endian if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) { uint *uintBuf = (uint *) image.data.data(); for (uint i = 0; i < image.data.size()/sizeof(uint); ++i) { *uintBuf = ntohl(*uintBuf); ++uintBuf; } } QImage iconImage(image.width, image.height, QImage::Format_ARGB32 ); memcpy(iconImage.bits(), (uchar*)image.data.data(), iconImage.byteCount()); return QPixmap::fromImage(iconImage); } QIcon StatusNotifierItemSource::imageVectorToPixmap(const KDbusImageVector &vector) const { QIcon icon; for (int i = 0; ipixmap(KIconLoader::SizeSmall, KIconLoader::SizeSmall); QPainter p(&m_iconPixmap); const int size = KIconLoader::SizeSmall/2; p.drawPixmap(QRect(size, size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); //if an m_icon exactly that size wasn't found don't add it to the vector m_iconPixmap = icon->pixmap(KIconLoader::SizeSmallMedium, KIconLoader::SizeSmallMedium); if (m_iconPixmap.width() == KIconLoader::SizeSmallMedium) { const int size = KIconLoader::SizeSmall/2; QPainter p(&m_iconPixmap); p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); } m_iconPixmap = icon->pixmap(KIconLoader::SizeMedium, KIconLoader::SizeMedium); if (m_iconPixmap.width() == KIconLoader::SizeMedium) { const int size = KIconLoader::SizeSmall/2; QPainter p(&m_iconPixmap); p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); } m_iconPixmap = icon->pixmap(KIconLoader::SizeLarge, KIconLoader::SizeLarge); if (m_iconPixmap.width() == KIconLoader::SizeLarge) { const int size = KIconLoader::SizeSmall; QPainter p(&m_iconPixmap); p.drawPixmap(QRect(m_iconPixmap.width()-size, m_iconPixmap.height()-size, size, size), overlay->pixmap(size, size), QRect(0,0,size,size)); p.end(); tmp.addPixmap(m_iconPixmap); } // We can't do 'm_icon->addPixmap()' because if 'm_icon' uses KIconEngine, // it will ignore the added pixmaps. This is not a bug in KIconEngine, // QIcon::addPixmap() doc says: "Custom m_icon engines are free to ignore // additionally added pixmaps". *icon = tmp; //hopefully huge and enormous not necessary right now, since it's quite costly } void StatusNotifierItemSource::activate(int x, int y) { if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Activate"), x, y); } } void StatusNotifierItemSource::secondaryActivate(int x, int y) { if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("SecondaryActivate"), x, y); } } void StatusNotifierItemSource::scroll(int delta, const QString &direction) { if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("Scroll"), delta, direction); } } void StatusNotifierItemSource::contextMenu(int x, int y) { if (m_menuImporter) { m_menuImporter->updateMenu(); } else { qWarning() << "Could not find DBusMenu interface, falling back to calling ContextMenu()"; if (m_statusNotifierItemInterface && m_statusNotifierItemInterface->isValid()) { m_statusNotifierItemInterface->call(QDBus::NoBlock, QStringLiteral("ContextMenu"), x, y); } } } #include "statusnotifieritemsource.moc"