File indexing completed on 2024-06-23 03:56:33

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2009 Marco Martin <notmart@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "kstatusnotifieritem.h"
0009 #include "config-kstatusnotifieritem.h"
0010 #include "debug_p.h"
0011 #include "kstatusnotifieritemprivate_p.h"
0012 
0013 #include <QApplication>
0014 #include <QImage>
0015 #include <QMenu>
0016 #include <QMessageBox>
0017 #include <QMovie>
0018 #include <QPainter>
0019 #include <QPixmap>
0020 #include <QPushButton>
0021 #include <QStandardPaths>
0022 #ifdef Q_OS_MACOS
0023 #include <QFontDatabase>
0024 #endif
0025 
0026 #define slots
0027 #include <QtWidgets/private/qwidgetwindow_p.h>
0028 #undef slots
0029 
0030 #ifdef QT_DBUS_LIB
0031 #include "kstatusnotifieritemdbus_p.h"
0032 
0033 #include <QDBusConnection>
0034 
0035 #if HAVE_DBUSMENUQT
0036 #include "libdbusmenu-qt/dbusmenuexporter.h"
0037 #endif // HAVE_DBUSMENUQT
0038 #endif
0039 
0040 #include <QTimer>
0041 #include <kwindowsystem.h>
0042 
0043 #if HAVE_X11
0044 #include <KWindowInfo>
0045 #include <KX11Extras>
0046 #endif
0047 
0048 #ifdef Q_OS_MACOS
0049 namespace MacUtils
0050 {
0051 void setBadgeLabelText(const QString &s);
0052 }
0053 #endif
0054 #include <cstdlib>
0055 
0056 static const char s_statusNotifierWatcherServiceName[] = "org.kde.StatusNotifierWatcher";
0057 static const int s_legacyTrayIconSize = 24;
0058 
0059 KStatusNotifierItem::KStatusNotifierItem(QObject *parent)
0060     : QObject(parent)
0061     , d(new KStatusNotifierItemPrivate(this))
0062 {
0063     d->init(QString());
0064 }
0065 
0066 KStatusNotifierItem::KStatusNotifierItem(const QString &id, QObject *parent)
0067     : QObject(parent)
0068     , d(new KStatusNotifierItemPrivate(this))
0069 {
0070     d->init(id);
0071 }
0072 
0073 KStatusNotifierItem::~KStatusNotifierItem()
0074 {
0075 #ifdef QT_DBUS_LIB
0076     delete d->statusNotifierWatcher;
0077     delete d->notificationsClient;
0078 #endif
0079     delete d->systemTrayIcon;
0080     if (!qApp->closingDown()) {
0081         delete d->menu;
0082     }
0083     if (d->associatedWindow) {
0084         KWindowSystem::self()->disconnect(d->associatedWindow);
0085     }
0086 }
0087 
0088 QString KStatusNotifierItem::id() const
0089 {
0090     // qCDebug(LOG_KSTATUSNOTIFIERITEM) << "id requested" << d->id;
0091     return d->id;
0092 }
0093 
0094 void KStatusNotifierItem::setCategory(const ItemCategory category)
0095 {
0096     d->category = category;
0097 }
0098 
0099 KStatusNotifierItem::ItemStatus KStatusNotifierItem::status() const
0100 {
0101     return d->status;
0102 }
0103 
0104 KStatusNotifierItem::ItemCategory KStatusNotifierItem::category() const
0105 {
0106     return d->category;
0107 }
0108 
0109 void KStatusNotifierItem::setTitle(const QString &title)
0110 {
0111     d->title = title;
0112 }
0113 
0114 void KStatusNotifierItem::setStatus(const ItemStatus status)
0115 {
0116     if (d->status == status) {
0117         return;
0118     }
0119 
0120     d->status = status;
0121 
0122 #ifdef QT_DBUS_LIB
0123     Q_EMIT d->statusNotifierItemDBus->NewStatus(
0124         QString::fromLatin1(metaObject()->enumerator(metaObject()->indexOfEnumerator("ItemStatus")).valueToKey(d->status)));
0125 #endif
0126     if (d->systemTrayIcon) {
0127         d->syncLegacySystemTrayIcon();
0128     }
0129 }
0130 
0131 // normal icon
0132 
0133 void KStatusNotifierItem::setIconByName(const QString &name)
0134 {
0135     if (d->iconName == name) {
0136         return;
0137     }
0138 
0139     d->iconName = name;
0140 
0141 #ifdef QT_DBUS_LIB
0142     d->serializedIcon = KDbusImageVector();
0143     Q_EMIT d->statusNotifierItemDBus->NewIcon();
0144 #endif
0145 
0146     if (d->systemTrayIcon) {
0147         d->systemTrayIcon->setIcon(QIcon::fromTheme(name));
0148     }
0149 }
0150 
0151 QString KStatusNotifierItem::iconName() const
0152 {
0153     return d->iconName;
0154 }
0155 
0156 void KStatusNotifierItem::setIconByPixmap(const QIcon &icon)
0157 {
0158     if (d->iconName.isEmpty() && d->icon.cacheKey() == icon.cacheKey()) {
0159         return;
0160     }
0161 
0162     d->iconName.clear();
0163 
0164 #ifdef QT_DBUS_LIB
0165     d->serializedIcon = d->iconToVector(icon);
0166     Q_EMIT d->statusNotifierItemDBus->NewIcon();
0167 #endif
0168 
0169     d->icon = icon;
0170     if (d->systemTrayIcon) {
0171         d->systemTrayIcon->setIcon(icon);
0172     }
0173 }
0174 
0175 QIcon KStatusNotifierItem::iconPixmap() const
0176 {
0177     return d->icon;
0178 }
0179 
0180 void KStatusNotifierItem::setOverlayIconByName(const QString &name)
0181 {
0182     if (d->overlayIconName == name) {
0183         return;
0184     }
0185 
0186     d->overlayIconName = name;
0187 #ifdef QT_DBUS_LIB
0188     Q_EMIT d->statusNotifierItemDBus->NewOverlayIcon();
0189 #endif
0190     if (d->systemTrayIcon) {
0191         QPixmap iconPixmap = QIcon::fromTheme(d->iconName).pixmap(s_legacyTrayIconSize, s_legacyTrayIconSize);
0192         if (!name.isEmpty()) {
0193             QPixmap overlayPixmap = QIcon::fromTheme(d->overlayIconName).pixmap(s_legacyTrayIconSize / 2, s_legacyTrayIconSize / 2);
0194             QPainter p(&iconPixmap);
0195             p.drawPixmap(iconPixmap.width() - overlayPixmap.width(), iconPixmap.height() - overlayPixmap.height(), overlayPixmap);
0196             p.end();
0197         }
0198         d->systemTrayIcon->setIcon(iconPixmap);
0199     }
0200 }
0201 
0202 QString KStatusNotifierItem::overlayIconName() const
0203 {
0204     return d->overlayIconName;
0205 }
0206 
0207 void KStatusNotifierItem::setOverlayIconByPixmap(const QIcon &icon)
0208 {
0209     if (d->overlayIconName.isEmpty() && d->overlayIcon.cacheKey() == icon.cacheKey()) {
0210         return;
0211     }
0212 
0213     d->overlayIconName.clear();
0214 
0215 #ifdef QT_DBUS_LIB
0216     d->serializedOverlayIcon = d->iconToVector(icon);
0217     Q_EMIT d->statusNotifierItemDBus->NewOverlayIcon();
0218 #endif
0219 
0220     d->overlayIcon = icon;
0221     if (d->systemTrayIcon) {
0222         QPixmap iconPixmap = d->icon.pixmap(s_legacyTrayIconSize, s_legacyTrayIconSize);
0223         QPixmap overlayPixmap = d->overlayIcon.pixmap(s_legacyTrayIconSize / 2, s_legacyTrayIconSize / 2);
0224 
0225         QPainter p(&iconPixmap);
0226         p.drawPixmap(iconPixmap.width() - overlayPixmap.width(), iconPixmap.height() - overlayPixmap.height(), overlayPixmap);
0227         p.end();
0228         d->systemTrayIcon->setIcon(iconPixmap);
0229     }
0230 }
0231 
0232 QIcon KStatusNotifierItem::overlayIconPixmap() const
0233 {
0234     return d->overlayIcon;
0235 }
0236 
0237 // Icons and movie for requesting attention state
0238 
0239 void KStatusNotifierItem::setAttentionIconByName(const QString &name)
0240 {
0241     if (d->attentionIconName == name) {
0242         return;
0243     }
0244 
0245     d->attentionIconName = name;
0246 
0247 #ifdef QT_DBUS_LIB
0248     d->serializedAttentionIcon = KDbusImageVector();
0249     Q_EMIT d->statusNotifierItemDBus->NewAttentionIcon();
0250 #endif
0251 }
0252 
0253 QString KStatusNotifierItem::attentionIconName() const
0254 {
0255     return d->attentionIconName;
0256 }
0257 
0258 void KStatusNotifierItem::setAttentionIconByPixmap(const QIcon &icon)
0259 {
0260     if (d->attentionIconName.isEmpty() && d->attentionIcon.cacheKey() == icon.cacheKey()) {
0261         return;
0262     }
0263 
0264     d->attentionIconName.clear();
0265     d->attentionIcon = icon;
0266 
0267 #ifdef QT_DBUS_LIB
0268     d->serializedAttentionIcon = d->iconToVector(icon);
0269     Q_EMIT d->statusNotifierItemDBus->NewAttentionIcon();
0270 #endif
0271 }
0272 
0273 QIcon KStatusNotifierItem::attentionIconPixmap() const
0274 {
0275     return d->attentionIcon;
0276 }
0277 
0278 void KStatusNotifierItem::setAttentionMovieByName(const QString &name)
0279 {
0280     if (d->movieName == name) {
0281         return;
0282     }
0283 
0284     d->movieName = name;
0285 
0286     delete d->movie;
0287     d->movie = nullptr;
0288 
0289 #ifdef QT_DBUS_LIB
0290     Q_EMIT d->statusNotifierItemDBus->NewAttentionIcon();
0291 #endif
0292 
0293     if (d->systemTrayIcon) {
0294         d->movie = new QMovie(d->movieName);
0295         d->systemTrayIcon->setMovie(d->movie);
0296     }
0297 }
0298 
0299 QString KStatusNotifierItem::attentionMovieName() const
0300 {
0301     return d->movieName;
0302 }
0303 
0304 // ToolTip
0305 
0306 #ifdef Q_OS_MACOS
0307 static void setTrayToolTip(KStatusNotifierLegacyIcon *systemTrayIcon, const QString &title, const QString &subTitle)
0308 {
0309     if (systemTrayIcon) {
0310         bool tEmpty = title.isEmpty(), stEmpty = subTitle.isEmpty();
0311         if (tEmpty) {
0312             if (!stEmpty) {
0313                 systemTrayIcon->setToolTip(subTitle);
0314             } else {
0315                 systemTrayIcon->setToolTip(title);
0316             }
0317         } else {
0318             if (stEmpty) {
0319                 systemTrayIcon->setToolTip(title);
0320             } else {
0321                 systemTrayIcon->setToolTip(title + QStringLiteral("\n") + subTitle);
0322             }
0323         }
0324     }
0325 }
0326 #else
0327 static void setTrayToolTip(KStatusNotifierLegacyIcon *systemTrayIcon, const QString &title, const QString &)
0328 {
0329     if (systemTrayIcon) {
0330         systemTrayIcon->setToolTip(title);
0331     }
0332 }
0333 #endif
0334 
0335 void KStatusNotifierItem::setToolTip(const QString &iconName, const QString &title, const QString &subTitle)
0336 {
0337     if (d->toolTipIconName == iconName && d->toolTipTitle == title && d->toolTipSubTitle == subTitle) {
0338         return;
0339     }
0340 
0341     d->toolTipIconName = iconName;
0342 
0343     d->toolTipTitle = title;
0344     setTrayToolTip(d->systemTrayIcon, title, subTitle);
0345     d->toolTipSubTitle = subTitle;
0346 
0347 #ifdef QT_DBUS_LIB
0348     d->serializedToolTipIcon = KDbusImageVector();
0349     Q_EMIT d->statusNotifierItemDBus->NewToolTip();
0350 #endif
0351 }
0352 
0353 void KStatusNotifierItem::setToolTip(const QIcon &icon, const QString &title, const QString &subTitle)
0354 {
0355     if (d->toolTipIconName.isEmpty() && d->toolTipIcon.cacheKey() == icon.cacheKey() //
0356         && d->toolTipTitle == title //
0357         && d->toolTipSubTitle == subTitle) {
0358         return;
0359     }
0360 
0361     d->toolTipIconName.clear();
0362     d->toolTipIcon = icon;
0363 
0364     d->toolTipTitle = title;
0365     setTrayToolTip(d->systemTrayIcon, title, subTitle);
0366 
0367     d->toolTipSubTitle = subTitle;
0368 #ifdef QT_DBUS_LIB
0369     d->serializedToolTipIcon = d->iconToVector(icon);
0370     Q_EMIT d->statusNotifierItemDBus->NewToolTip();
0371 #endif
0372 }
0373 
0374 void KStatusNotifierItem::setToolTipIconByName(const QString &name)
0375 {
0376     if (d->toolTipIconName == name) {
0377         return;
0378     }
0379 
0380     d->toolTipIconName = name;
0381 #ifdef QT_DBUS_LIB
0382     d->serializedToolTipIcon = KDbusImageVector();
0383     Q_EMIT d->statusNotifierItemDBus->NewToolTip();
0384 #endif
0385 }
0386 
0387 QString KStatusNotifierItem::toolTipIconName() const
0388 {
0389     return d->toolTipIconName;
0390 }
0391 
0392 void KStatusNotifierItem::setToolTipIconByPixmap(const QIcon &icon)
0393 {
0394     if (d->toolTipIconName.isEmpty() && d->toolTipIcon.cacheKey() == icon.cacheKey()) {
0395         return;
0396     }
0397 
0398     d->toolTipIconName.clear();
0399     d->toolTipIcon = icon;
0400 
0401 #ifdef QT_DBUS_LIB
0402     d->serializedToolTipIcon = d->iconToVector(icon);
0403     Q_EMIT d->statusNotifierItemDBus->NewToolTip();
0404 #endif
0405 }
0406 
0407 QIcon KStatusNotifierItem::toolTipIconPixmap() const
0408 {
0409     return d->toolTipIcon;
0410 }
0411 
0412 void KStatusNotifierItem::setToolTipTitle(const QString &title)
0413 {
0414     if (d->toolTipTitle == title) {
0415         return;
0416     }
0417 
0418     d->toolTipTitle = title;
0419 
0420 #ifdef QT_DBUS_LIB
0421     Q_EMIT d->statusNotifierItemDBus->NewToolTip();
0422 #endif
0423     setTrayToolTip(d->systemTrayIcon, title, d->toolTipSubTitle);
0424 }
0425 
0426 QString KStatusNotifierItem::toolTipTitle() const
0427 {
0428     return d->toolTipTitle;
0429 }
0430 
0431 void KStatusNotifierItem::setToolTipSubTitle(const QString &subTitle)
0432 {
0433     if (d->toolTipSubTitle == subTitle) {
0434         return;
0435     }
0436 
0437     d->toolTipSubTitle = subTitle;
0438 #ifdef QT_DBUS_LIB
0439     Q_EMIT d->statusNotifierItemDBus->NewToolTip();
0440 #else
0441     setTrayToolTip(d->systemTrayIcon, d->toolTipTitle, subTitle);
0442 #endif
0443 }
0444 
0445 QString KStatusNotifierItem::toolTipSubTitle() const
0446 {
0447     return d->toolTipSubTitle;
0448 }
0449 
0450 void KStatusNotifierItem::setContextMenu(QMenu *menu)
0451 {
0452     if (d->menu && d->menu != menu) {
0453         d->menu->removeEventFilter(this);
0454         delete d->menu;
0455     }
0456 
0457     if (!menu) {
0458         d->menu = nullptr;
0459         return;
0460     }
0461 
0462     if (d->systemTrayIcon) {
0463         d->systemTrayIcon->setContextMenu(menu);
0464     } else if (d->menu != menu) {
0465         if (getenv("KSNI_NO_DBUSMENU")) {
0466             // This is a hack to make it possible to disable DBusMenu in an
0467             // application. The string "/NO_DBUSMENU" must be the same as in
0468             // DBusSystemTrayWidget::findDBusMenuInterface() in the Plasma
0469             // systemtray applet.
0470             d->menuObjectPath = QStringLiteral("/NO_DBUSMENU");
0471             menu->installEventFilter(this);
0472         } else {
0473             d->menuObjectPath = QStringLiteral("/MenuBar");
0474 #if HAVE_DBUSMENUQT
0475             new DBusMenuExporter(d->menuObjectPath, menu, d->statusNotifierItemDBus->dbusConnection());
0476             Q_EMIT d->statusNotifierItemDBus->NewMenu();
0477 #endif
0478         }
0479 
0480         connect(menu, SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow()));
0481     }
0482 
0483     d->menu = menu;
0484     Qt::WindowFlags oldFlags = d->menu->windowFlags();
0485     d->menu->setParent(nullptr);
0486     d->menu->setWindowFlags(oldFlags);
0487 }
0488 
0489 QMenu *KStatusNotifierItem::contextMenu() const
0490 {
0491     return d->menu;
0492 }
0493 
0494 void KStatusNotifierItem::setAssociatedWindow(QWindow *associatedWindow)
0495 {
0496     if (associatedWindow) {
0497         d->associatedWindow = associatedWindow;
0498         d->associatedWindow->installEventFilter(this);
0499         d->associatedWindowPos = QPoint(-1, -1);
0500     } else {
0501         if (d->associatedWindow) {
0502             d->associatedWindow->removeEventFilter(this);
0503             d->associatedWindow = nullptr;
0504         }
0505     }
0506 
0507     if (d->systemTrayIcon) {
0508         delete d->systemTrayIcon;
0509         d->systemTrayIcon = nullptr;
0510         d->setLegacySystemTrayEnabled(true);
0511     }
0512 
0513     if (d->associatedWindow) {
0514         QAction *action = d->actionCollection.value(QStringLiteral("minimizeRestore"));
0515 
0516         if (!action) {
0517             action = new QAction(this);
0518             d->actionCollection.insert(QStringLiteral("minimizeRestore"), action);
0519             action->setText(tr("&Minimize", "@action:inmenu"));
0520             action->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize")));
0521             connect(action, SIGNAL(triggered(bool)), this, SLOT(minimizeRestore()));
0522         }
0523 
0524 #if HAVE_X11
0525         if (KWindowSystem::isPlatformX11()) {
0526             KWindowInfo info(d->associatedWindow->winId(), NET::WMDesktop);
0527             d->onAllDesktops = info.onAllDesktops();
0528         }
0529 #endif
0530     } else {
0531         if (d->menu && d->hasQuit) {
0532             QAction *action = d->actionCollection.value(QStringLiteral("minimizeRestore"));
0533             if (action) {
0534                 d->menu->removeAction(action);
0535             }
0536         }
0537 
0538         d->onAllDesktops = false;
0539     }
0540 }
0541 
0542 QWindow *KStatusNotifierItem::associatedWindow() const
0543 {
0544     return d->associatedWindow;
0545 }
0546 
0547 QList<QAction *> KStatusNotifierItem::actionCollection() const
0548 {
0549     return d->actionCollection.values();
0550 }
0551 
0552 void KStatusNotifierItem::addAction(const QString &name, QAction *action)
0553 {
0554     d->actionCollection.insert(name, action);
0555 }
0556 
0557 void KStatusNotifierItem::removeAction(const QString &name)
0558 {
0559     d->actionCollection.remove(name);
0560 }
0561 
0562 QAction *KStatusNotifierItem::action(const QString &name) const
0563 {
0564     return d->actionCollection.value(name);
0565 }
0566 
0567 void KStatusNotifierItem::setStandardActionsEnabled(bool enabled)
0568 {
0569     if (d->standardActionsEnabled == enabled) {
0570         return;
0571     }
0572 
0573     d->standardActionsEnabled = enabled;
0574 
0575     if (d->menu && !enabled && d->hasQuit) {
0576         QAction *action = d->actionCollection.value(QStringLiteral("minimizeRestore"));
0577         if (action) {
0578             d->menu->removeAction(action);
0579         }
0580 
0581         action = d->actionCollection.value(QStringLiteral("quit"));
0582         if (action) {
0583             d->menu->removeAction(action);
0584         }
0585 
0586         d->hasQuit = false;
0587     }
0588 }
0589 
0590 bool KStatusNotifierItem::standardActionsEnabled() const
0591 {
0592     return d->standardActionsEnabled;
0593 }
0594 
0595 void KStatusNotifierItem::showMessage(const QString &title, const QString &message, const QString &icon, int timeout)
0596 {
0597 #ifdef QT_DBUS_LIB
0598     if (!d->notificationsClient) {
0599         d->notificationsClient = new org::freedesktop::Notifications(QStringLiteral("org.freedesktop.Notifications"),
0600                                                                      QStringLiteral("/org/freedesktop/Notifications"),
0601                                                                      QDBusConnection::sessionBus());
0602     }
0603 
0604     uint id = 0;
0605     QVariantMap hints;
0606 
0607     QString desktopFileName = QGuiApplication::desktopFileName();
0608     if (!desktopFileName.isEmpty()) {
0609         // handle apps which set the desktopFileName property with filename suffix,
0610         // due to unclear API dox (https://bugreports.qt.io/browse/QTBUG-75521)
0611         if (desktopFileName.endsWith(QLatin1String(".desktop"))) {
0612             desktopFileName.chop(8);
0613         }
0614         hints.insert(QStringLiteral("desktop-entry"), desktopFileName);
0615     }
0616 
0617     d->notificationsClient->Notify(d->title, id, icon, title, message, QStringList(), hints, timeout);
0618 #else
0619     if (d->systemTrayIcon) {
0620         // Growl is not needed anymore for QSystemTrayIcon::showMessage() since OS X 10.8
0621         d->systemTrayIcon->showMessage(title, message, QSystemTrayIcon::Information, timeout);
0622     }
0623 #endif
0624 }
0625 
0626 QString KStatusNotifierItem::title() const
0627 {
0628     return d->title;
0629 }
0630 
0631 void KStatusNotifierItem::activate(const QPoint &pos)
0632 {
0633     // if the user activated the icon the NeedsAttention state is no longer necessary
0634     // FIXME: always true?
0635     if (d->status == NeedsAttention) {
0636         d->status = Active;
0637 #ifdef Q_OS_MACOS
0638         MacUtils::setBadgeLabelText(QString());
0639 #endif
0640 #ifdef QT_DBUS_LIB
0641         Q_EMIT d->statusNotifierItemDBus->NewStatus(
0642             QString::fromLatin1(metaObject()->enumerator(metaObject()->indexOfEnumerator("ItemStatus")).valueToKey(d->status)));
0643 #endif
0644     }
0645 
0646     if (d->menu && d->menu->isVisible()) {
0647         d->menu->hide();
0648     }
0649 
0650     if (!d->associatedWindow) {
0651         Q_EMIT activateRequested(true, pos);
0652         return;
0653     }
0654 
0655     d->checkVisibility(pos);
0656 }
0657 
0658 void KStatusNotifierItem::hideAssociatedWindow()
0659 {
0660     if (!d->associatedWindow) {
0661         return;
0662     }
0663     d->minimizeRestore(false);
0664 }
0665 
0666 QString KStatusNotifierItem::providedToken() const
0667 {
0668 #ifdef QT_DBUS_LIB
0669     return d->statusNotifierItemDBus->m_xdgActivationToken;
0670 #else
0671     return {};
0672 #endif
0673 }
0674 
0675 bool KStatusNotifierItemPrivate::checkVisibility(QPoint pos, bool perform)
0676 {
0677     // mapped = visible (but possibly obscured)
0678     const bool mapped = associatedWindow->isVisible() && !(associatedWindow->windowState() & Qt::WindowMinimized);
0679 
0680     //    - not mapped -> show, raise, focus
0681     //    - mapped
0682     //        - obscured -> raise, focus
0683     //        - not obscured -> hide
0684     // info1.mappingState() != NET::Visible -> window on another desktop?
0685     if (!mapped) {
0686         if (perform) {
0687             minimizeRestore(true);
0688             Q_EMIT q->activateRequested(true, pos);
0689         }
0690 
0691         return true;
0692 #if HAVE_X11
0693     } else if (QGuiApplication::platformName() == QLatin1String("xcb")) {
0694         if (KWindowSystem::isPlatformX11()) {
0695             const KWindowInfo info1(associatedWindow->winId(), NET::XAWMState | NET::WMState | NET::WMDesktop);
0696             QListIterator<WId> it(KX11Extras::stackingOrder());
0697             it.toBack();
0698             while (it.hasPrevious()) {
0699                 WId id = it.previous();
0700                 if (id == associatedWindow->winId()) {
0701                     break;
0702                 }
0703 
0704                 KWindowInfo info2(id, NET::WMDesktop | NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType);
0705 
0706                 if (info2.mappingState() != NET::Visible) {
0707                     continue; // not visible on current desktop -> ignore
0708                 }
0709 
0710                 if (!info2.geometry().intersects(associatedWindow->geometry())) {
0711                     continue; // not obscuring the window -> ignore
0712                 }
0713 
0714                 if (!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove)) {
0715                     continue; // obscured by window kept above -> ignore
0716                 }
0717 
0718                 /* clang-format off */
0719             static constexpr auto flags = (NET::NormalMask
0720                                            | NET::DesktopMask
0721                                            | NET::DockMask
0722                                            | NET::ToolbarMask
0723                                            | NET::MenuMask
0724                                            | NET::DialogMask
0725                                            | NET::OverrideMask
0726                                            | NET::TopMenuMask
0727                                            | NET::UtilityMask
0728                                            | NET::SplashMask);
0729                 /* clang-format on */
0730                 NET::WindowType type = info2.windowType(flags);
0731 
0732                 if (type == NET::Dock || type == NET::TopMenu) {
0733                     continue; // obscured by dock or topmenu -> ignore
0734                 }
0735 
0736                 if (perform) {
0737                     KX11Extras::forceActiveWindow(associatedWindow->winId());
0738                     Q_EMIT q->activateRequested(true, pos);
0739                 }
0740 
0741                 return true;
0742             }
0743 
0744             // not on current desktop?
0745             if (!info1.isOnCurrentDesktop()) {
0746                 if (perform) {
0747                     KWindowSystem::activateWindow(associatedWindow);
0748                     Q_EMIT q->activateRequested(true, pos);
0749                 }
0750 
0751                 return true;
0752             }
0753 
0754             if (perform) {
0755                 minimizeRestore(false); // hide
0756                 Q_EMIT q->activateRequested(false, pos);
0757             }
0758         }
0759         return false;
0760 #endif
0761     } else {
0762         if (perform) {
0763             minimizeRestore(false); // hide
0764             Q_EMIT q->activateRequested(false, pos);
0765         }
0766         return false;
0767     }
0768 
0769     return true;
0770 }
0771 
0772 bool KStatusNotifierItem::eventFilter(QObject *watched, QEvent *event)
0773 {
0774     if (watched == d->associatedWindow) {
0775         if (event->type() == QEvent::Show) {
0776             d->associatedWindow->setPosition(d->associatedWindowPos);
0777         } else if (event->type() == QEvent::Hide) {
0778             d->associatedWindowPos = d->associatedWindow->position();
0779         }
0780     }
0781 
0782     if (d->systemTrayIcon == nullptr) {
0783         // FIXME: ugly ugly workaround to weird QMenu's focus problems
0784         if (watched == d->menu
0785             && (event->type() == QEvent::WindowDeactivate
0786                 || (event->type() == QEvent::MouseButtonRelease && static_cast<QMouseEvent *>(event)->button() == Qt::LeftButton))) {
0787             // put at the back of even queue to let the action activate anyways
0788             QTimer::singleShot(0, this, [this]() {
0789                 d->hideMenu();
0790             });
0791         }
0792     }
0793     return false;
0794 }
0795 
0796 // KStatusNotifierItemPrivate
0797 
0798 const int KStatusNotifierItemPrivate::s_protocolVersion = 0;
0799 
0800 KStatusNotifierItemPrivate::KStatusNotifierItemPrivate(KStatusNotifierItem *item)
0801     : q(item)
0802     , category(KStatusNotifierItem::ApplicationStatus)
0803     , status(KStatusNotifierItem::Passive)
0804     , movie(nullptr)
0805     , systemTrayIcon(nullptr)
0806     , menu(nullptr)
0807     , associatedWindow(nullptr)
0808     , titleAction(nullptr)
0809     , hasQuit(false)
0810     , onAllDesktops(false)
0811     , standardActionsEnabled(true)
0812 {
0813 }
0814 
0815 void KStatusNotifierItemPrivate::init(const QString &extraId)
0816 {
0817     QWidget *parentWidget = qobject_cast<QWidget *>(q->parent());
0818 
0819     q->setAssociatedWindow(parentWidget ? parentWidget->window()->windowHandle() : nullptr);
0820 #ifdef QT_DBUS_LIB
0821     qDBusRegisterMetaType<KDbusImageStruct>();
0822     qDBusRegisterMetaType<KDbusImageVector>();
0823     qDBusRegisterMetaType<KDbusToolTipStruct>();
0824 
0825     statusNotifierItemDBus = new KStatusNotifierItemDBus(q);
0826 
0827     QDBusServiceWatcher *watcher = new QDBusServiceWatcher(QString::fromLatin1(s_statusNotifierWatcherServiceName),
0828                                                            QDBusConnection::sessionBus(),
0829                                                            QDBusServiceWatcher::WatchForOwnerChange,
0830                                                            q);
0831     QObject::connect(watcher, SIGNAL(serviceOwnerChanged(QString, QString, QString)), q, SLOT(serviceChange(QString, QString, QString)));
0832 #endif
0833 
0834     // create a default menu, just like in KSystemtrayIcon
0835     QMenu *m = new QMenu(parentWidget);
0836 
0837     title = QGuiApplication::applicationDisplayName();
0838     if (title.isEmpty()) {
0839         title = QCoreApplication::applicationName();
0840     }
0841 #ifdef Q_OS_MACOS
0842     // OS X doesn't have texted separators so we emulate QAction::addSection():
0843     // we first add an action with the desired text (title) and icon
0844     titleAction = m->addAction(qApp->windowIcon(), title);
0845     // this action should be disabled
0846     titleAction->setEnabled(false);
0847     // Give the titleAction a visible menu icon:
0848     // Systray icon and menu ("menu extra") are often used by applications that provide no other interface.
0849     // It is thus reasonable to show the application icon in the menu; Finder, Dock and App Switcher
0850     // all show it in addition to the application name (and Apple's input "menu extra" also shows icons).
0851     titleAction->setIconVisibleInMenu(true);
0852     m->addAction(titleAction);
0853     // now add a regular separator
0854     m->addSeparator();
0855 #else
0856     titleAction = m->addSection(qApp->windowIcon(), title);
0857     m->setTitle(title);
0858 #endif
0859     q->setContextMenu(m);
0860 
0861     QAction *action = new QAction(q);
0862     action->setText(KStatusNotifierItem::tr("Quit", "@action:inmenu"));
0863     action->setIcon(QIcon::fromTheme(QStringLiteral("application-exit")));
0864     // cannot yet convert to function-pointer-based connect:
0865     // some apps like kalarm or korgac have a hack to rewire the connection
0866     // of the "quit" action to a own slot, and rely on the name-based slot to disconnect
0867     // TODO: extend KStatusNotifierItem API to support such needs
0868     QObject::connect(action, SIGNAL(triggered()), q, SLOT(maybeQuit()));
0869     actionCollection.insert(QStringLiteral("quit"), action);
0870 
0871     id = title;
0872     if (!extraId.isEmpty()) {
0873         id.append(QLatin1Char('_')).append(extraId);
0874     }
0875 
0876     // Init iconThemePath to the app folder for now
0877     iconThemePath = QStandardPaths::locate(QStandardPaths::AppDataLocation, QStringLiteral("icons"), QStandardPaths::LocateDirectory);
0878 
0879     registerToDaemon();
0880 }
0881 
0882 void KStatusNotifierItemPrivate::registerToDaemon()
0883 {
0884     bool useLegacy = false;
0885 #ifdef QT_DBUS_LIB
0886     qCDebug(LOG_KSTATUSNOTIFIERITEM) << "Registering a client interface to the KStatusNotifierWatcher";
0887     if (!statusNotifierWatcher) {
0888         statusNotifierWatcher = new org::kde::StatusNotifierWatcher(QString::fromLatin1(s_statusNotifierWatcherServiceName),
0889                                                                     QStringLiteral("/StatusNotifierWatcher"),
0890                                                                     QDBusConnection::sessionBus());
0891     }
0892 
0893     if (statusNotifierWatcher->isValid()) {
0894         // get protocol version in async way
0895         QDBusMessage msg = QDBusMessage::createMethodCall(QString::fromLatin1(s_statusNotifierWatcherServiceName),
0896                                                           QStringLiteral("/StatusNotifierWatcher"),
0897                                                           QStringLiteral("org.freedesktop.DBus.Properties"),
0898                                                           QStringLiteral("Get"));
0899         msg.setArguments(QVariantList{QStringLiteral("org.kde.StatusNotifierWatcher"), QStringLiteral("ProtocolVersion")});
0900         QDBusPendingCall async = QDBusConnection::sessionBus().asyncCall(msg);
0901         QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(async, q);
0902         QObject::connect(watcher, &QDBusPendingCallWatcher::finished, q, [this, watcher] {
0903             watcher->deleteLater();
0904             QDBusPendingReply<QVariant> reply = *watcher;
0905             if (reply.isError()) {
0906                 qCDebug(LOG_KSTATUSNOTIFIERITEM) << "Failed to read protocol version of KStatusNotifierWatcher";
0907                 setLegacySystemTrayEnabled(true);
0908             } else {
0909                 bool ok = false;
0910                 const int protocolVersion = reply.value().toInt(&ok);
0911                 if (ok && protocolVersion == s_protocolVersion) {
0912                     statusNotifierWatcher->RegisterStatusNotifierItem(statusNotifierItemDBus->service());
0913                     setLegacySystemTrayEnabled(false);
0914                 } else {
0915                     qCDebug(LOG_KSTATUSNOTIFIERITEM) << "KStatusNotifierWatcher has incorrect protocol version";
0916                     setLegacySystemTrayEnabled(true);
0917                 }
0918             }
0919         });
0920     } else {
0921         qCDebug(LOG_KSTATUSNOTIFIERITEM) << "KStatusNotifierWatcher not reachable";
0922         useLegacy = true;
0923     }
0924 #else
0925     useLegacy = true;
0926 #endif
0927     setLegacySystemTrayEnabled(useLegacy);
0928 }
0929 
0930 void KStatusNotifierItemPrivate::serviceChange(const QString &name, const QString &oldOwner, const QString &newOwner)
0931 {
0932     Q_UNUSED(name)
0933     if (newOwner.isEmpty()) {
0934         // unregistered
0935         qCDebug(LOG_KSTATUSNOTIFIERITEM) << "Connection to the KStatusNotifierWatcher lost";
0936         setLegacyMode(true);
0937 #ifdef QT_DBUS_LIB
0938         delete statusNotifierWatcher;
0939         statusNotifierWatcher = nullptr;
0940 #endif
0941     } else if (oldOwner.isEmpty()) {
0942         // registered
0943         setLegacyMode(false);
0944     }
0945 }
0946 
0947 void KStatusNotifierItemPrivate::setLegacyMode(bool legacy)
0948 {
0949     if (legacy) {
0950         // unregistered
0951         setLegacySystemTrayEnabled(true);
0952     } else {
0953         // registered
0954         registerToDaemon();
0955     }
0956 }
0957 
0958 void KStatusNotifierItemPrivate::legacyWheelEvent(int delta)
0959 {
0960 #ifdef QT_DBUS_LIB
0961     statusNotifierItemDBus->Scroll(delta, QStringLiteral("vertical"));
0962 #endif
0963 }
0964 
0965 void KStatusNotifierItemPrivate::legacyActivated(QSystemTrayIcon::ActivationReason reason)
0966 {
0967     if (reason == QSystemTrayIcon::MiddleClick) {
0968         Q_EMIT q->secondaryActivateRequested(systemTrayIcon->geometry().topLeft());
0969     } else if (reason == QSystemTrayIcon::Trigger) {
0970         q->activate(systemTrayIcon->geometry().topLeft());
0971     }
0972 }
0973 
0974 void KStatusNotifierItemPrivate::setLegacySystemTrayEnabled(bool enabled)
0975 {
0976     if (enabled == (systemTrayIcon != nullptr)) {
0977         // already in the correct state
0978         return;
0979     }
0980 
0981     if (enabled) {
0982         bool isKde = !qEnvironmentVariableIsEmpty("KDE_FULL_SESSION") || qgetenv("XDG_CURRENT_DESKTOP") == "KDE";
0983         if (!systemTrayIcon && !isKde) {
0984             if (!QSystemTrayIcon::isSystemTrayAvailable()) {
0985                 return;
0986             }
0987             systemTrayIcon = new KStatusNotifierLegacyIcon(q);
0988             syncLegacySystemTrayIcon();
0989             systemTrayIcon->setToolTip(toolTipTitle);
0990             systemTrayIcon->show();
0991             QObject::connect(systemTrayIcon, SIGNAL(wheel(int)), q, SLOT(legacyWheelEvent(int)));
0992             QObject::connect(systemTrayIcon, SIGNAL(activated(QSystemTrayIcon::ActivationReason)), q, SLOT(legacyActivated(QSystemTrayIcon::ActivationReason)));
0993         } else if (isKde) {
0994             // prevent infinite recursion if the KDE platform plugin is loaded
0995             // but SNI is not available; see bug 350785
0996             qCWarning(LOG_KSTATUSNOTIFIERITEM) << "env says KDE is running but SNI unavailable -- check "
0997                                                   "KDE_FULL_SESSION and XDG_CURRENT_DESKTOP";
0998             return;
0999         }
1000 
1001         if (menu) {
1002             menu->setWindowFlags(Qt::Popup);
1003         }
1004     } else {
1005         delete systemTrayIcon;
1006         systemTrayIcon = nullptr;
1007 
1008         if (menu) {
1009             menu->setWindowFlags(Qt::Window);
1010         }
1011     }
1012 
1013     if (menu) {
1014         QMenu *m = menu;
1015         menu = nullptr;
1016         q->setContextMenu(m);
1017     }
1018 }
1019 
1020 void KStatusNotifierItemPrivate::syncLegacySystemTrayIcon()
1021 {
1022     if (status == KStatusNotifierItem::NeedsAttention) {
1023 #ifdef Q_OS_MACOS
1024         MacUtils::setBadgeLabelText(QString(QChar(0x26a0)) /*QStringLiteral("!")*/);
1025         if (attentionIconName.isNull() && attentionIcon.isNull()) {
1026             // code adapted from kmail's KMSystemTray::updateCount()
1027             int overlaySize = 22;
1028             QIcon attnIcon = qApp->windowIcon();
1029             if (!attnIcon.availableSizes().isEmpty()) {
1030                 overlaySize = attnIcon.availableSizes().at(0).width();
1031             }
1032             QFont labelFont = QFontDatabase::systemFont(QFontDatabase::GeneralFont);
1033             labelFont.setBold(true);
1034             QFontMetrics qfm(labelFont);
1035             float attnHeight = overlaySize * 0.667;
1036             if (qfm.height() > attnHeight) {
1037                 float labelSize = attnHeight;
1038                 labelFont.setPointSizeF(labelSize);
1039             }
1040             // Paint the label in a pixmap
1041             QPixmap overlayPixmap(overlaySize, overlaySize);
1042             overlayPixmap.fill(Qt::transparent);
1043 
1044             QPainter p(&overlayPixmap);
1045             p.setFont(labelFont);
1046             p.setBrush(Qt::NoBrush);
1047             // this sort of badge/label is red on OS X
1048             p.setPen(QColor(224, 0, 0));
1049             p.setOpacity(1.0);
1050             // use U+2022, the Unicode bullet
1051             p.drawText(overlayPixmap.rect(), Qt::AlignRight | Qt::AlignTop, QString(QChar(0x2022)));
1052             p.end();
1053 
1054             QPixmap iconPixmap = attnIcon.pixmap(overlaySize, overlaySize);
1055             QPainter pp(&iconPixmap);
1056             pp.drawPixmap(0, 0, overlayPixmap);
1057             pp.end();
1058             systemTrayIcon->setIcon(iconPixmap);
1059         } else
1060 #endif
1061         {
1062             if (!movieName.isNull()) {
1063                 if (!movie) {
1064                     movie = new QMovie(movieName);
1065                 }
1066                 systemTrayIcon->setMovie(movie);
1067             } else if (!attentionIconName.isNull()) {
1068                 systemTrayIcon->setIcon(QIcon::fromTheme(attentionIconName));
1069             } else {
1070                 systemTrayIcon->setIcon(attentionIcon);
1071             }
1072         }
1073     } else {
1074 #ifdef Q_OS_MACOS
1075         if (!iconName.isNull()) {
1076             QIcon theIcon = QIcon::fromTheme(iconName);
1077             systemTrayIcon->setIconWithMask(theIcon, status == KStatusNotifierItem::Passive);
1078         } else {
1079             systemTrayIcon->setIconWithMask(icon, status == KStatusNotifierItem::Passive);
1080         }
1081         MacUtils::setBadgeLabelText(QString());
1082 #else
1083         if (!iconName.isNull()) {
1084             systemTrayIcon->setIcon(QIcon::fromTheme(iconName));
1085         } else {
1086             systemTrayIcon->setIcon(icon);
1087         }
1088 #endif
1089     }
1090 
1091     systemTrayIcon->setToolTip(toolTipTitle);
1092 }
1093 
1094 void KStatusNotifierItemPrivate::contextMenuAboutToShow()
1095 {
1096     if (!hasQuit && standardActionsEnabled) {
1097         // we need to add the actions to the menu afterwards so that these items
1098         // appear at the _END_ of the menu
1099         menu->addSeparator();
1100         if (associatedWindow) {
1101             QAction *action = actionCollection.value(QStringLiteral("minimizeRestore"));
1102 
1103             if (action) {
1104                 menu->addAction(action);
1105             }
1106         }
1107 
1108         QAction *action = actionCollection.value(QStringLiteral("quit"));
1109 
1110         if (action) {
1111             menu->addAction(action);
1112         }
1113 
1114         hasQuit = true;
1115     }
1116 
1117     if (associatedWindow) {
1118         QAction *action = actionCollection.value(QStringLiteral("minimizeRestore"));
1119         if (checkVisibility(QPoint(0, 0), false)) {
1120             action->setText(KStatusNotifierItem::tr("&Restore", "@action:inmenu"));
1121             action->setIcon(QIcon::fromTheme(QStringLiteral("window-restore")));
1122         } else {
1123             action->setText(KStatusNotifierItem::tr("&Minimize", "@action:inmenu"));
1124             action->setIcon(QIcon::fromTheme(QStringLiteral("window-minimize")));
1125         }
1126     }
1127 }
1128 
1129 void KStatusNotifierItemPrivate::maybeQuit()
1130 {
1131     QString caption = QGuiApplication::applicationDisplayName();
1132     if (caption.isEmpty()) {
1133         caption = QCoreApplication::applicationName();
1134     }
1135 
1136     const QString title = KStatusNotifierItem::tr("Confirm Quit From System Tray", "@title:window");
1137     const QString query = KStatusNotifierItem::tr("<qt>Are you sure you want to quit <b>%1</b>?</qt>").arg(caption);
1138 
1139     auto *dialog = new QMessageBox(QMessageBox::Question, title, query, QMessageBox::NoButton);
1140     dialog->setAttribute(Qt::WA_DeleteOnClose);
1141 
1142     auto *quitButton = dialog->addButton(KStatusNotifierItem::tr("Quit", "@action:button"), QMessageBox::AcceptRole);
1143     quitButton->setIcon(QIcon::fromTheme(QStringLiteral("application-exit")));
1144     dialog->addButton(QMessageBox::Cancel);
1145     QObject::connect(dialog, &QDialog::accepted, qApp, &QApplication::quit);
1146     dialog->show();
1147     dialog->windowHandle()->setTransientParent(associatedWindow);
1148 }
1149 
1150 void KStatusNotifierItemPrivate::minimizeRestore()
1151 {
1152     q->activate(systemTrayIcon ? systemTrayIcon->geometry().topLeft() : QPoint(0, 0));
1153 }
1154 
1155 void KStatusNotifierItemPrivate::hideMenu()
1156 {
1157     menu->hide();
1158 }
1159 
1160 void KStatusNotifierItemPrivate::minimizeRestore(bool show)
1161 {
1162 #if HAVE_X11
1163     if (KWindowSystem::isPlatformX11()) {
1164         KWindowInfo info(associatedWindow->winId(), NET::WMDesktop);
1165 
1166         if (show) {
1167             if (onAllDesktops) {
1168                 KX11Extras::setOnAllDesktops(associatedWindow->winId(), true);
1169             } else {
1170                 KX11Extras::setCurrentDesktop(info.desktop());
1171             }
1172         } else {
1173             onAllDesktops = info.onAllDesktops();
1174         }
1175     }
1176 #endif
1177 
1178     if (show) {
1179         Qt::WindowState state = (Qt::WindowState)(associatedWindow->windowState() & ~Qt::WindowMinimized);
1180         associatedWindow->setWindowState(state);
1181         // Work around https://bugreports.qt.io/browse/QTBUG-120316
1182         if (auto *widgetwindow = static_cast<QWidgetWindow*>(associatedWindow->qt_metacast("QWidgetWindow"))) {
1183             widgetwindow->widget()->show();
1184         } else {
1185             associatedWindow->show();
1186         }
1187         associatedWindow->raise();
1188         KWindowSystem::activateWindow(associatedWindow);
1189     } else {
1190         // Work around https://bugreports.qt.io/browse/QTBUG-120316
1191         if (auto *widgetwindow = static_cast<QWidgetWindow*>(associatedWindow->qt_metacast("QWidgetWindow"))) {
1192             widgetwindow->widget()->hide();
1193         } else {
1194             associatedWindow->hide();
1195         }
1196     }
1197 }
1198 
1199 #ifdef QT_DBUS_LIB
1200 KDbusImageStruct KStatusNotifierItemPrivate::imageToStruct(const QImage &image)
1201 {
1202     KDbusImageStruct icon;
1203     icon.width = image.size().width();
1204     icon.height = image.size().height();
1205     if (image.format() == QImage::Format_ARGB32) {
1206         icon.data = QByteArray((char *)image.bits(), image.sizeInBytes());
1207     } else {
1208         QImage image32 = image.convertToFormat(QImage::Format_ARGB32);
1209         icon.data = QByteArray((char *)image32.bits(), image32.sizeInBytes());
1210     }
1211 
1212     // swap to network byte order if we are little endian
1213     if (QSysInfo::ByteOrder == QSysInfo::LittleEndian) {
1214         quint32 *uintBuf = (quint32 *)icon.data.data();
1215         for (uint i = 0; i < icon.data.size() / sizeof(quint32); ++i) {
1216             *uintBuf = qToBigEndian(*uintBuf);
1217             ++uintBuf;
1218         }
1219     }
1220 
1221     return icon;
1222 }
1223 
1224 KDbusImageVector KStatusNotifierItemPrivate::iconToVector(const QIcon &icon)
1225 {
1226     KDbusImageVector iconVector;
1227 
1228     QPixmap iconPixmap;
1229 
1230     // if an icon exactly that size wasn't found don't add it to the vector
1231     const auto lstSizes = icon.availableSizes();
1232     for (QSize size : lstSizes) {
1233         iconPixmap = icon.pixmap(size);
1234         iconVector.append(imageToStruct(iconPixmap.toImage()));
1235     }
1236 
1237     return iconVector;
1238 }
1239 #endif
1240 
1241 #include "moc_kstatusnotifieritem.cpp"
1242 #include "moc_kstatusnotifieritemprivate_p.cpp"