diff --git a/applets/notifications/plugin/draghelper.h b/applets/notifications/plugin/draghelper.h index 6c6230739..7c3e11c41 100644 --- a/applets/notifications/plugin/draghelper.h +++ b/applets/notifications/plugin/draghelper.h @@ -1,53 +1,53 @@ /*************************************************************************** * Copyright (C) 2013 by Eike Hein * * * * 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 DRAGHELPER_H #define DRAGHELPER_H #include #include #include class QQuickItem; class DragHelper : public QObject { Q_OBJECT Q_PROPERTY(bool dragActive READ dragActive NOTIFY dragActiveChanged) public: - DragHelper(QObject *parent = nullptr); + explicit DragHelper(QObject *parent = nullptr); ~DragHelper(); bool dragActive() const; Q_INVOKABLE bool isDrag(int oldX, int oldY, int newX, int newY) const; Q_INVOKABLE void startDrag(QQuickItem* item, const QUrl &url = QUrl(), const QPixmap &pixmap = QPixmap()); Q_SIGNALS: void dragActiveChanged(); private: Q_INVOKABLE void doDrag(QQuickItem* item, const QUrl &url = QUrl(), const QPixmap &pixmap = QPixmap()); bool m_dragActive = false; }; #endif diff --git a/applets/notifications/plugin/notificationshelper.h b/applets/notifications/plugin/notificationshelper.h index 64a169815..7bbf337ff 100644 --- a/applets/notifications/plugin/notificationshelper.h +++ b/applets/notifications/plugin/notificationshelper.h @@ -1,95 +1,95 @@ /* Copyright (C) 2014 Martin Klapetek This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #ifndef NOTIFICATIONSHELPER_H #define NOTIFICATIONSHELPER_H #include #include #include #include class QQuickWindow; class QTimer; class QReadWriteLock; class NotificationsHelper : public QObject { Q_OBJECT Q_PROPERTY(PositionOnScreen popupLocation MEMBER m_popupLocation WRITE setPopupLocation NOTIFY popupLocationChanged) public: enum PositionOnScreen { Default, // Follows the panel TopLeft, TopCenter, TopRight, Left, Center, Right, BottomLeft, BottomCenter, BottomRight }; Q_ENUM(PositionOnScreen) - NotificationsHelper(QObject *parent = nullptr); + explicit NotificationsHelper(QObject *parent = nullptr); ~NotificationsHelper() override; Q_INVOKABLE void addNotificationPopup(QObject *win); Q_INVOKABLE void closePopup(const QString &sourceName); Q_INVOKABLE void setPlasmoidScreenGeometry(const QRect &geometry); void setPopupLocation(PositionOnScreen popupLocation); /** * Fills the popup with data from notificationData * and puts the popup on proper place on screen. * If there's no space on screen for the notification, * it's queued and displayed as soon as there's space for it */ Q_INVOKABLE void displayNotification(const QVariantMap ¬ificationData); Q_SIGNALS: void popupLocationChanged(); void popupShown(QQuickWindow* popup); // void plasmoidScreenChanged(); private Q_SLOTS: void onPopupClosed(); void processQueues(); void processShow(); void processHide(); private: void repositionPopups(); QList m_popupsOnScreen; QList m_availablePopups; QHash m_sourceMap; QRect m_plasmoidScreen; PositionOnScreen m_popupLocation; int m_offset; bool m_busy; QList m_hideQueue; QList m_showQueue; QReadWriteLock *m_mutex; QTimer *m_dispatchTimer; }; #endif // NOTIFICATIONSHELPER_H diff --git a/applets/notifications/plugin/thumbnailer.cpp b/applets/notifications/plugin/thumbnailer.cpp index 72a64323a..b913066d1 100644 --- a/applets/notifications/plugin/thumbnailer.cpp +++ b/applets/notifications/plugin/thumbnailer.cpp @@ -1,241 +1,241 @@ /* Copyright (C) 2016 Kai Uwe Broulik This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2.1 of the License, or (at your option) any later version. This library 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 Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA */ #include "thumbnailer.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include Thumbnailer::Thumbnailer(QObject *parent) : QObject(parent) { } Thumbnailer::~Thumbnailer() = default; void Thumbnailer::classBegin() { } void Thumbnailer::componentComplete() { m_inited = true; generatePreview(); } QUrl Thumbnailer::url() const { return m_url; } void Thumbnailer::setUrl(const QUrl &url) { if (m_url != url) { m_url = url; emit urlChanged(); generatePreview(); } } QSize Thumbnailer::size() const { return m_size; } void Thumbnailer::setSize(const QSize &size) { if (m_size != size) { m_size = size; emit sizeChanged(); generatePreview(); } } bool Thumbnailer::hasPreview() const { return !m_pixmap.isNull(); } QPixmap Thumbnailer::pixmap() const { return m_pixmap; } QSize Thumbnailer::pixmapSize() const { return m_pixmap.size(); } QString Thumbnailer::iconName() const { return m_iconName; } bool Thumbnailer::menuVisible() const { return m_menuVisible; } void Thumbnailer::showContextMenu(int x, int y, const QString &path, QQuickItem *ctx) { if (!ctx || !ctx->window()) { return; } const QUrl url(path); if (!url.isValid()) { return; } KFileItem fileItem(url); QMenu *menu = new QMenu(); menu->setAttribute(Qt::WA_DeleteOnClose, true); connect(menu, &QMenu::aboutToHide, this, [this] { m_menuVisible = false; emit menuVisibleChanged(); }); if (KProtocolManager::supportsListing(url)) { - QAction *openContainingFolderAction = menu->addAction(QIcon::fromTheme("folder-open"), i18n("Open Containing Folder")); + QAction *openContainingFolderAction = menu->addAction(QIcon::fromTheme(QStringLiteral("folder-open")), i18n("Open Containing Folder")); connect(openContainingFolderAction, &QAction::triggered, [url] { KIO::highlightInFileManager({url}); }); } menu->addSeparator(); // KStandardAction? But then the Ctrl+C shortcut makes no sense in this context QAction *copyAction = menu->addAction(QIcon::fromTheme(QStringLiteral("edit-copy")), i18n("&Copy")); connect(copyAction, &QAction::triggered, [fileItem] { // inspired by KDirModel::mimeData() QMimeData *data = new QMimeData(); // who cleans it up? KUrlMimeData::setUrls({fileItem.url()}, {fileItem.mostLocalUrl()}, data); QApplication::clipboard()->setMimeData(data); }); KFileItemActions *actions = new KFileItemActions(menu); KFileItemListProperties itemProperties(KFileItemList({fileItem})); actions->setItemListProperties(itemProperties); actions->addOpenWithActionsTo(menu); actions->addServiceActionsTo(menu); actions->addPluginActionsTo(menu); - QAction *propertiesAction = menu->addAction(QIcon::fromTheme("document-properties"), i18n("Properties")); + QAction *propertiesAction = menu->addAction(QIcon::fromTheme(QStringLiteral("document-properties")), i18n("Properties")); connect(propertiesAction, &QAction::triggered, [fileItem] { KPropertiesDialog *dialog = new KPropertiesDialog(fileItem.url()); dialog->setAttribute(Qt::WA_DeleteOnClose); dialog->show(); }); //this is a workaround where Qt will fail to realise a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [ctx]() { if (ctx->window()->mouseGrabberItem()) { ctx->window()->mouseGrabberItem()->ungrabMouse(); } }; QTimer::singleShot(0, ctx, ungrabMouseHack); //end workaround QPoint pos; if (x == -1 && y == -1) { // align "bottom left of ctx" menu->adjustSize(); pos = ctx->mapToGlobal(QPointF(0, ctx->height())).toPoint(); if (!qApp->isRightToLeft()) { pos.rx() += ctx->width(); pos.rx() -= menu->width(); } } else { pos = ctx->mapToGlobal(QPointF(x, y)).toPoint(); } menu->popup(pos); m_menuVisible = true; emit menuVisibleChanged(); } void Thumbnailer::generatePreview() { if (!m_inited) { return; } if (!m_url.isValid() || !m_url.isLocalFile() || !m_size.isValid()) { return; } auto maxSize = qMax(m_size.width(), m_size.height()); KIO::PreviewJob *job = KIO::filePreview(KFileItemList({KFileItem(m_url)}), QSize(maxSize,maxSize)); job->setIgnoreMaximumSize(true); connect(job, &KIO::PreviewJob::gotPreview, this, [this](const KFileItem &item, const QPixmap &preview) { Q_UNUSED(item); m_pixmap = preview; emit pixmapChanged(); if (!m_iconName.isEmpty()) { m_iconName.clear(); emit iconNameChanged(); } }); connect(job, &KIO::PreviewJob::failed, this, [this](const KFileItem &item) { m_pixmap = QPixmap(); emit pixmapChanged(); const QString &iconName = item.determineMimeType().iconName(); if (m_iconName != iconName) { m_iconName = iconName; emit iconNameChanged(); } }); job->start(); } diff --git a/applets/systemtray/container/systemtraycontainer.cpp b/applets/systemtray/container/systemtraycontainer.cpp index 4222957ec..f784d7460 100644 --- a/applets/systemtray/container/systemtraycontainer.cpp +++ b/applets/systemtray/container/systemtraycontainer.cpp @@ -1,147 +1,147 @@ /*************************************************************************** * 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 "systemtraycontainer.h" #include "debug.h" #include #include #include #include SystemTrayContainer::SystemTrayContainer(QObject *parent, const QVariantList &args) : Plasma::Applet(parent, args) { } SystemTrayContainer::~SystemTrayContainer() { if (destroyed()) { m_innerContainment->destroy(); } } void SystemTrayContainer::init() { Applet::init(); //in the first creation we immediately create the systray: so it's accessible during desktop scripting uint id = config().readEntry("SystrayContainmentId", 0); if (id == 0) { ensureSystrayExists(); } } void SystemTrayContainer::ensureSystrayExists() { if (m_innerContainment) { return; } Plasma::Containment *cont = containment(); if (!cont) { return; } Plasma::Corona *c = cont->corona(); if (!c) { return; } uint id = config().readEntry("SystrayContainmentId", 0); if (id > 0) { foreach (Plasma::Containment *candidate, c->containments()) { if (candidate->id() == id) { m_innerContainment = candidate; break; } } qCDebug(SYSTEM_TRAY_CONTAINER) << "Containment id" << id << "that used to be a system tray was deleted"; //id = 0; } if (!m_innerContainment) { - m_innerContainment = c->createContainment("org.kde.plasma.private.systemtray", QVariantList() << "org.kde.plasma:force-create"); + m_innerContainment = c->createContainment(QStringLiteral("org.kde.plasma.private.systemtray"), QVariantList() << "org.kde.plasma:force-create"); config().writeEntry("SystrayContainmentId", m_innerContainment->id()); } if (!m_innerContainment) { return; } m_innerContainment->setParent(this); connect(containment(), &Plasma::Containment::screenChanged, m_innerContainment.data(), &Plasma::Containment::reactToScreenChange); if (formFactor() == Plasma::Types::Horizontal || formFactor() == Plasma::Types::Vertical) { m_innerContainment->setFormFactor(formFactor()); } else { m_innerContainment->setFormFactor(Plasma::Types::Horizontal); } m_innerContainment->setLocation(location()); m_internalSystray = m_innerContainment->property("_plasma_graphicObject").value(); emit internalSystrayChanged(); actions()->addAction("configure", m_innerContainment->actions()->action("configure")); connect(m_innerContainment.data(), &Plasma::Containment::configureRequested, this, [this](Plasma::Applet *applet) { emit containment()->configureRequested(applet); } ); if (m_internalSystray) { //don't let internal systray manage context menus m_internalSystray->setAcceptedMouseButtons(Qt::NoButton); } //replace internal remove action with ours m_innerContainment->actions()->addAction("remove", actions()->action("remove")); } void SystemTrayContainer::constraintsEvent(Plasma::Types::Constraints constraints) { if (constraints & Plasma::Types::LocationConstraint) { if (m_innerContainment) { m_innerContainment->setLocation(location()); } } if (constraints & Plasma::Types::FormFactorConstraint) { if (m_innerContainment) { if (formFactor() == Plasma::Types::Horizontal || formFactor() == Plasma::Types::Vertical) { m_innerContainment->setFormFactor(formFactor()); } else { m_innerContainment->setFormFactor(Plasma::Types::Horizontal); } } } if (constraints & Plasma::Types::UiReadyConstraint) { ensureSystrayExists(); } } QQuickItem *SystemTrayContainer::internalSystray() { return m_internalSystray; } K_EXPORT_PLASMA_APPLET_WITH_JSON(systemtraycontainer, SystemTrayContainer, "metadata.json") #include "systemtraycontainer.moc" diff --git a/applets/systemtray/systemtray.cpp b/applets/systemtray/systemtray.cpp index 9e94866de..f88e76deb 100644 --- a/applets/systemtray/systemtray.cpp +++ b/applets/systemtray/systemtray.cpp @@ -1,580 +1,580 @@ /*************************************************************************** * 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 class PlasmoidModel: public QStandardItemModel { public: explicit PlasmoidModel(QObject *parent = nullptr) : 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()->listAppletMetaData(QString())) { if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationArea")) != "true") { continue; } m_systrayApplets[info.pluginId()] = KPluginInfo(info); if (info.isEnabledByDefault()) { m_defaultPlasmoids += info.pluginId(); } const QString dbusactivation = info.value(QStringLiteral("X-Plasma-DBusActivationService")); if (!dbusactivation.isEmpty()) { qCDebug(SYSTEM_TRAY) << "ST Found DBus-able Applet: " << info.pluginId() << dbusactivation; m_dbusActivatableTasks[info.pluginId()] = dbusactivation; } } } void SystemTray::newTask(const QString &task) { foreach (Plasma::Applet *applet, applets()) { if (!applet->pluginMetaData().isValid()) { continue; } //only allow one instance per applet if (task == applet->pluginMetaData().pluginId()) { //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->pluginMetaData().isValid() && task == applet->pluginMetaData().pluginId()) { //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); } } } 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); //this is a workaround where Qt will fail to realise a mouse has been released // this happens if a window which does not accept focus spawns a new window that takes focus and X grab // whilst the mouse is depressed // https://bugreports.qt.io/browse/QTBUG-59044 // this causes the next click to go missing //by releasing manually we avoid that situation auto ungrabMouseHack = [appletInterface]() { if (appletInterface->window() && appletInterface->window()->mouseGrabberItem()) { appletInterface->window()->mouseGrabberItem()->ungrabMouse(); } }; QTimer::singleShot(0, appletInterface, ungrabMouseHack); //end workaround 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())); } KAcceleratorManager::manage(desktopMenu); desktopMenu->winId(); desktopMenu->windowHandle()->setTransientParent(appletInterface->window()); desktopMenu->popup(pos.toPoint()); } QString SystemTray::plasmoidCategory(QQuickItem *appletInterface) const { if (!appletInterface) { return QStringLiteral("UnknownCategory"); } Plasma::Applet *applet = appletInterface->property("_plasma_applet").value(); if (!applet || !applet->pluginMetaData().isValid()) { return QStringLiteral("UnknownCategory"); } const QString cat = applet->pluginMetaData().value(QStringLiteral("X-Plasma-NotificationAreaCategory")); if (cat.isEmpty()) { return QStringLiteral("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(); } } KAcceleratorManager::manage(menu); 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); } bool SystemTray::isSystemTrayApplet(const QString &appletId) { return m_systrayApplets.contains(appletId); } void SystemTray::restoreContents(KConfigGroup &group) { Q_UNUSED(group); - //NOTE: RestoreContents shouldnn't do anything here because is too soon, so have an empty reimplementation + //NOTE: RestoreContents shouldn'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->pluginMetaData().isValid()) { applet->config().parent().deleteGroup(); applet->deleteLater(); } else { const QString task = applet->pluginMetaData().pluginId(); 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); } m_availablePlasmoidsModel->sort(0 /*column*/); } 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"