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"