File indexing completed on 2024-04-14 14:20:25

0001 /* This file is part of the KDE libraries
0002 
0003     Copyright (C) 1999 Matthias Ettrich (ettrich@kde.org)
0004     Copyright (c) 2007      by Charles Connell <charles@connells.org>
0005     Copyright (C) 2008 Lukas Appelhans <l.appelhans@gmx.de>
0006 
0007     This library is free software; you can redistribute it and/or
0008     modify it under the terms of the GNU Library General Public
0009     License as published by the Free Software Foundation; either
0010     version 2 of the License, or (at your option) any later version.
0011 
0012     This library is distributed in the hope that it will be useful,
0013     but WITHOUT ANY WARRANTY; without even the implied warranty of
0014     MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
0015     Library General Public License for more details.
0016 
0017     You should have received a copy of the GNU Library General Public License
0018     along with this library; see the file COPYING.LIB.  If not, write to
0019     the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
0020     Boston, MA 02110-1301, USA.
0021 */
0022 
0023 #include "ksystemtrayicon.h"
0024 #include "kaboutdata.h"
0025 #include "kaction.h"
0026 #include "kcomponentdata.h"
0027 #include "klocalizedstring.h"
0028 #include "kmenu.h"
0029 #include "kmessagebox.h"
0030 #include "kshortcut.h"
0031 #include "kactioncollection.h"
0032 #include "kstandardaction.h"
0033 #include <kglobal.h>
0034 #include <kwindowsystem.h>
0035 
0036 #include <config-kdelibs4support.h>
0037 #ifdef Q_OS_WIN
0038 #include <windows.h>
0039 #endif
0040 
0041 #include <kiconloader.h>
0042 #include <kapplication.h>
0043 #include <kconfiggroup.h>
0044 
0045 #include <QMouseEvent>
0046 #include <QToolButton>
0047 #include <QMovie>
0048 #include <QPointer>
0049 
0050 #ifdef Q_OS_WIN
0051 class KSystemTrayIconPrivate : public QObject
0052 #else
0053 class KSystemTrayIconPrivate
0054 #endif
0055 {
0056 public:
0057     KSystemTrayIconPrivate(KSystemTrayIcon *trayIcon, QWidget *parent)
0058         : q(trayIcon)
0059     {
0060         actionCollection = new KActionCollection(trayIcon);
0061         hasQuit = false;
0062         onAllDesktops = false;
0063         window = parent;
0064         movie = nullptr;
0065 #ifdef Q_OS_WIN
0066         if (window) {
0067             window->installEventFilter(this);
0068         }
0069 #endif
0070     }
0071 
0072     ~KSystemTrayIconPrivate()
0073     {
0074 #ifdef Q_OS_WIN
0075         if (window) {
0076             window->removeEventFilter(this);
0077         }
0078 #endif
0079         delete actionCollection;
0080         delete menu;
0081     }
0082 
0083     void _k_slotNewFrame()
0084     {
0085         q->setIcon(QIcon(movie->currentPixmap()));
0086     }
0087 
0088 #ifdef Q_OS_WIN
0089     bool eventFilter(QObject *obj, QEvent *ev)
0090     {
0091         if (ev->type() == QEvent::ActivationChange) {
0092             dwTickCount = GetTickCount();
0093         }
0094         return QObject::eventFilter(obj, ev);
0095     }
0096     DWORD dwTickCount;
0097 #endif
0098 
0099     KSystemTrayIcon *q;
0100     KActionCollection *actionCollection;
0101     KMenu *menu;
0102     QWidget *window;
0103     QAction *titleAction;
0104     bool onAllDesktops : 1; // valid only when the parent widget was hidden
0105     bool hasQuit : 1;
0106     QPointer<QMovie> movie;
0107 };
0108 
0109 KSystemTrayIcon::KSystemTrayIcon(QWidget *parent)
0110     : QSystemTrayIcon(parent),
0111       d(new KSystemTrayIconPrivate(this, parent))
0112 {
0113     init(parent);
0114 }
0115 
0116 KSystemTrayIcon::KSystemTrayIcon(const QString &icon, QWidget *parent)
0117     : QSystemTrayIcon(loadIcon(icon), parent),
0118       d(new KSystemTrayIconPrivate(this, parent))
0119 {
0120     init(parent);
0121 }
0122 
0123 KSystemTrayIcon::KSystemTrayIcon(const QIcon &icon, QWidget *parent)
0124     : QSystemTrayIcon(icon, parent),
0125       d(new KSystemTrayIconPrivate(this, parent))
0126 {
0127     init(parent);
0128 }
0129 
0130 KSystemTrayIcon::KSystemTrayIcon(QMovie *movie, QWidget *parent)
0131     : QSystemTrayIcon(parent),
0132       d(new KSystemTrayIconPrivate(this, parent))
0133 {
0134     init(parent);
0135     setMovie(movie);
0136 }
0137 
0138 void KSystemTrayIcon::init(QWidget *parent)
0139 {
0140     // Ensure that closing the last KMainWindow doesn't exit the application
0141     // if a system tray icon is still present.
0142     KGlobal::ref();
0143     d->menu = new KMenu(parent);
0144     d->titleAction = d->menu->addTitle(qApp->windowIcon(), KGlobal::caption());
0145     d->menu->setTitle(QGuiApplication::applicationDisplayName());
0146     connect(d->menu, SIGNAL(aboutToShow()), this, SLOT(contextMenuAboutToShow()));
0147     setContextMenu(d->menu);
0148 
0149     KStandardAction::quit(this, SLOT(maybeQuit()), d->actionCollection);
0150 
0151     if (parent) {
0152         QAction *action = d->actionCollection->addAction("minimizeRestore");
0153         action->setText(i18n("Minimize"));
0154         connect(action, SIGNAL(triggered(bool)), this, SLOT(minimizeRestoreAction()));
0155 
0156 #if HAVE_X11
0157         KWindowInfo info(parent->winId(), NET::WMDesktop);
0158         d->onAllDesktops = info.onAllDesktops();
0159 #else
0160         d->onAllDesktops = false;
0161 #endif
0162     } else {
0163         d->onAllDesktops = false;
0164     }
0165 
0166     connect(this, SIGNAL(activated(QSystemTrayIcon::ActivationReason)),
0167             SLOT(activateOrHide(QSystemTrayIcon::ActivationReason)));
0168 }
0169 
0170 QWidget *KSystemTrayIcon::parentWidget() const
0171 {
0172     return d->window;
0173 }
0174 
0175 KSystemTrayIcon::~KSystemTrayIcon()
0176 {
0177     delete d;
0178     KGlobal::deref();
0179 }
0180 
0181 void KSystemTrayIcon::contextMenuAboutToShow()
0182 {
0183     if (!d->hasQuit) {
0184         // we need to add the actions to the menu afterwards so that these items
0185         // appear at the _END_ of the menu
0186         d->menu->addSeparator();
0187         QAction *action = d->actionCollection->action("minimizeRestore");
0188 
0189         if (action) {
0190             d->menu->addAction(action);
0191         }
0192 
0193         action = d->actionCollection->action(KStandardAction::name(KStandardAction::Quit));
0194 
0195         if (action) {
0196             d->menu->addAction(action);
0197         }
0198 
0199         d->hasQuit = true;
0200     }
0201 
0202     if (d->window) {
0203         QAction *action = d->actionCollection->action("minimizeRestore");
0204         if (d->window->isVisible()) {
0205             action->setText(i18n("&Minimize"));
0206         } else {
0207             action->setText(i18n("&Restore"));
0208         }
0209     }
0210 }
0211 
0212 // called from the popup menu - always do what the menu entry says,
0213 // i.e. if the window is shown, no matter if active or not, the menu
0214 // entry is "minimize", otherwise it's "restore"
0215 void KSystemTrayIcon::minimizeRestoreAction()
0216 {
0217     if (d->window) {
0218         bool restore = !(d->window->isVisible());
0219         minimizeRestore(restore);
0220     }
0221 }
0222 
0223 void KSystemTrayIcon::maybeQuit()
0224 {
0225     QString caption = KGlobal::caption();
0226     QString query = i18n("<qt>Are you sure you want to quit <b>%1</b>?</qt>",
0227                          caption);
0228     if (KMessageBox::warningContinueCancel(d->window, query,
0229                                            i18n("Confirm Quit From System Tray"),
0230                                            KStandardGuiItem::quit(),
0231                                            KStandardGuiItem::cancel(),
0232                                            QString("systemtrayquit%1")
0233                                            .arg(caption)) !=
0234             KMessageBox::Continue) {
0235         return;
0236     }
0237 
0238     emit quitSelected();
0239     qApp->quit();
0240 }
0241 
0242 // if the window is not the active one, show it if needed, and activate it
0243 // (just like taskbar); otherwise hide it
0244 void KSystemTrayIcon::activateOrHide(QSystemTrayIcon::ActivationReason reasonCalled)
0245 {
0246     if (reasonCalled != QSystemTrayIcon::Trigger) {
0247         return;
0248     }
0249 
0250     QWidget *pw = d->window;
0251     if (!pw) {
0252         return;
0253     }
0254 #ifdef Q_OS_WIN
0255     // the problem is that we lose focus when the systray icon is activated
0256     // and we don't know the former active window
0257     // therefore we watch for activation event and use our stopwatch :)
0258     if (GetTickCount() - d->dwTickCount < 300) {
0259         // we were active in the last 300ms -> hide it
0260         minimizeRestore(false);
0261     } else {
0262         minimizeRestore(true);
0263     }
0264 #elif HAVE_X11
0265     KWindowInfo info1(pw->winId(), NET::XAWMState | NET::WMState);
0266     // mapped = visible (but possibly obscured)
0267     bool mapped = (info1.mappingState() == NET::Visible) && !info1.isMinimized();
0268 //    - not mapped -> show, raise, focus
0269 //    - mapped
0270 //        - obscured -> raise, focus
0271 //        - not obscured -> hide
0272     if (!mapped) {
0273         minimizeRestore(true);
0274     } else {
0275         QListIterator< WId > it(KWindowSystem::stackingOrder());
0276         it.toBack();
0277         while (it.hasPrevious()) {
0278             WId id = it.previous();
0279             if (id == pw->winId()) {
0280                 break;
0281             }
0282             KWindowInfo info2(id, NET::WMGeometry | NET::XAWMState | NET::WMState | NET::WMWindowType);
0283             if (info2.mappingState() != NET::Visible) {
0284                 continue;    // not visible on current desktop -> ignore
0285             }
0286             if (!info2.geometry().intersects(pw->geometry())) {
0287                 continue;    // not obscuring the window -> ignore
0288             }
0289             if (!info1.hasState(NET::KeepAbove) && info2.hasState(NET::KeepAbove)) {
0290                 continue;    // obscured by window kept above -> ignore
0291             }
0292             NET::WindowType type = info2.windowType(NET::NormalMask | NET::DesktopMask
0293                                                     | NET::DockMask | NET::ToolbarMask | NET::MenuMask | NET::DialogMask
0294                                                     | NET::OverrideMask | NET::TopMenuMask | NET::UtilityMask | NET::SplashMask);
0295             if (type == NET::Dock || type == NET::TopMenu) {
0296                 continue;    // obscured by dock or topmenu -> ignore
0297             }
0298             pw->raise();
0299             KWindowSystem::activateWindow(pw->winId());
0300             return;
0301         }
0302         minimizeRestore(false);   // hide
0303     }
0304 #endif
0305 }
0306 
0307 void KSystemTrayIcon::minimizeRestore(bool restore)
0308 {
0309     QWidget *pw = d->window;
0310     if (!pw) {
0311         return;
0312     }
0313 #if HAVE_X11
0314     KWindowInfo info(pw->winId(), NET::WMGeometry | NET::WMDesktop);
0315     if (restore) {
0316         if (d->onAllDesktops) {
0317             KWindowSystem::setOnAllDesktops(pw->winId(), true);
0318         } else {
0319             KWindowSystem::setCurrentDesktop(info.desktop());
0320         }
0321         pw->move(info.geometry().topLeft()); // avoid placement policies
0322         pw->show();
0323         pw->raise();
0324         KWindowSystem::activateWindow(pw->winId());
0325     } else {
0326         d->onAllDesktops = info.onAllDesktops();
0327         pw->hide();
0328     }
0329 #else
0330     if (restore) {
0331         pw->show();
0332         pw->raise();
0333         KWindowSystem::forceActiveWindow(pw->winId());
0334     } else {
0335         pw->hide();
0336     }
0337 #endif
0338 }
0339 
0340 KActionCollection *KSystemTrayIcon::actionCollection()
0341 {
0342     return d->actionCollection;
0343 }
0344 
0345 QIcon KSystemTrayIcon::loadIcon(const QString &icon, const KComponentData &componentData)
0346 {
0347     KConfigGroup cg(componentData.config(), "System Tray");
0348     const int iconWidth = cg.readEntry("systrayIconWidth", 22);
0349     return KIconLoader::global()->loadIcon(icon, KIconLoader::Panel, iconWidth);
0350 }
0351 
0352 void KSystemTrayIcon::toggleActive()
0353 {
0354     activateOrHide(QSystemTrayIcon::Trigger);
0355 }
0356 
0357 bool KSystemTrayIcon::parentWidgetTrayClose() const
0358 {
0359     if (kapp != nullptr && kapp->sessionSaving()) {
0360         return false;    // normal close
0361     }
0362     return true;
0363 }
0364 
0365 void KSystemTrayIcon::setContextMenuTitle(QAction *action)
0366 {
0367     // can never be null, and is always the requsted type, so no need to do null checks after casts.
0368     QToolButton *button = static_cast<QToolButton *>((static_cast<QWidgetAction *>(d->titleAction))->defaultWidget());
0369     button->setDefaultAction(action);
0370 }
0371 
0372 QAction *KSystemTrayIcon::contextMenuTitle() const
0373 {
0374     QToolButton *button = static_cast<QToolButton *>((static_cast<QWidgetAction *>(d->titleAction))->defaultWidget());
0375     return button->defaultAction();
0376 }
0377 
0378 void KSystemTrayIcon::setMovie(QMovie *m)
0379 {
0380     if (d->movie == m) {
0381         return;
0382     }
0383     delete d->movie;
0384     m->setParent(this);
0385     d->movie = m;
0386     connect(d->movie, SIGNAL(frameChanged(int)), this, SLOT(_k_slotNewFrame()));
0387     d->movie->setCacheMode(QMovie::CacheAll);
0388 }
0389 
0390 const QMovie *KSystemTrayIcon::movie() const
0391 {
0392     return d->movie;
0393 }
0394 
0395 #include "moc_ksystemtrayicon.cpp"