File indexing completed on 2024-04-21 16:30:37
0001 // SPDX-License-Identifier: GPL-3.0-or-later 0002 /* 0003 Copyright 2017 - 2020 Martin Koller, kollix@aon.at 0004 0005 This file is part of liquidshell. 0006 0007 liquidshell is free software: you can redistribute it and/or modify 0008 it under the terms of the GNU General Public License as published by 0009 the Free Software Foundation, either version 3 of the License, or 0010 (at your option) any later version. 0011 0012 liquidshell 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 0015 GNU General Public License for more details. 0016 0017 You should have received a copy of the GNU General Public License 0018 along with liquidshell. If not, see <http://www.gnu.org/licenses/>. 0019 */ 0020 0021 #include <SysTray.hxx> 0022 #include <DBusTypes.hxx> 0023 #include <NotificationServer.hxx> 0024 #include <Network.hxx> 0025 #include <DeviceNotifier.hxx> 0026 #include <Battery.hxx> 0027 #include <Bluetooth.hxx> 0028 0029 #ifdef WITH_PACKAGEKIT 0030 #include <PkUpdates.hxx> 0031 #endif 0032 0033 #include <QApplication> 0034 #include <QDBusConnection> 0035 #include <QDBusMessage> 0036 #include <QDBusPendingReply> 0037 #include <QDBusMetaType> 0038 #include <QDebug> 0039 0040 #include <KLocalizedString> 0041 0042 #include <cmath> 0043 0044 //-------------------------------------------------------------------------------- 0045 0046 static const QLatin1String WATCHER_SERVICE("org.kde.StatusNotifierWatcher"); 0047 0048 //-------------------------------------------------------------------------------- 0049 0050 SysTray::SysTray(DesktopPanel *parent) 0051 : QFrame(parent) 0052 { 0053 qRegisterMetaType<KDbusImageStruct>("KDbusImageStruct"); 0054 qRegisterMetaType<KDbusImageVector>("KDbusImageVector"); 0055 0056 qDBusRegisterMetaType<KDbusImageStruct>(); 0057 qDBusRegisterMetaType<KDbusImageVector>(); 0058 qDBusRegisterMetaType<KDbusToolTipStruct>(); 0059 0060 setFrameShape(QFrame::StyledPanel); 0061 setSizePolicy(QSizePolicy::Maximum, QSizePolicy::Preferred); 0062 0063 connect(parent, &DesktopPanel::rowsChanged, this, &SysTray::fill); 0064 0065 QHBoxLayout *hbox = new QHBoxLayout(this); 0066 hbox->setContentsMargins(QMargins(4, 0, 4, 0)); 0067 0068 vbox = new QVBoxLayout; 0069 vbox->setContentsMargins(QMargins()); 0070 vbox->setSpacing(4); 0071 0072 QFrame *separator = new QFrame; 0073 separator->setFrameStyle(QFrame::Plain); 0074 separator->setFrameShape(QFrame::VLine); 0075 0076 appsVbox = new QVBoxLayout; 0077 appsVbox->setContentsMargins(QMargins()); 0078 appsVbox->setSpacing(4); 0079 0080 hbox->addLayout(vbox); 0081 hbox->addWidget(separator); 0082 hbox->addLayout(appsVbox); 0083 0084 if ( QDBusConnection::sessionBus().isConnected() ) 0085 { 0086 serviceName = QString("org.kde.StatusNotifierHost-%1").arg(QApplication::applicationPid()); 0087 QDBusConnection::sessionBus().registerService(serviceName); 0088 0089 registerWatcher(); 0090 } 0091 0092 fill(); 0093 } 0094 0095 //-------------------------------------------------------------------------------- 0096 0097 void SysTray::fill() 0098 { 0099 // delete all internal widgets 0100 QLayoutItem *child; 0101 while ( (child = vbox->takeAt(0)) ) 0102 { 0103 if ( child->layout() ) 0104 { 0105 while ( QLayoutItem *widgetItem = child->layout()->takeAt(0) ) 0106 delete widgetItem->widget(); 0107 } 0108 0109 delete child; 0110 } 0111 0112 const int MAX_ROWS = qobject_cast<DesktopPanel *>(parentWidget())->getRows(); 0113 0114 QVector<QHBoxLayout *> rowsLayout(MAX_ROWS); 0115 for (int i = 0; i < MAX_ROWS; i++) 0116 { 0117 rowsLayout[i] = new QHBoxLayout; 0118 rowsLayout[i]->setContentsMargins(QMargins()); 0119 rowsLayout[i]->setSpacing(4); 0120 vbox->addLayout(rowsLayout[i]); 0121 } 0122 0123 QVector<QWidget *> internalWidgets = 0124 { 0125 notificationServer = new NotificationServer(this), 0126 new Network(this), 0127 new DeviceNotifier(this), 0128 new Battery(this), 0129 new Bluetooth(this), 0130 #ifdef WITH_PACKAGEKIT 0131 new PkUpdates(this) 0132 #endif 0133 }; 0134 0135 for (int i = 0; i < internalWidgets.count(); i++) 0136 rowsLayout[i % MAX_ROWS]->addWidget(internalWidgets[i], 0, Qt::AlignLeft); 0137 0138 for (int i = 0; i < MAX_ROWS; i++) 0139 rowsLayout[i]->addStretch(); 0140 0141 // notifier items 0142 0143 qDeleteAll(appsRows); 0144 appsRows.clear(); 0145 appsRows.resize(MAX_ROWS); 0146 for (int i = 0; i < MAX_ROWS; i++) 0147 { 0148 appsRows[i] = new QHBoxLayout; 0149 appsRows[i]->setContentsMargins(QMargins()); 0150 appsRows[i]->setSpacing(4); 0151 appsVbox->addLayout(appsRows[i]); 0152 } 0153 0154 arrangeNotifyItems(); 0155 } 0156 0157 //-------------------------------------------------------------------------------- 0158 0159 void SysTray::registerWatcher() 0160 { 0161 QDBusMessage msg = 0162 QDBusMessage::createMethodCall(WATCHER_SERVICE, "/StatusNotifierWatcher", 0163 "org.kde.StatusNotifierWatcher", 0164 "RegisterStatusNotifierHost"); 0165 0166 msg << serviceName; 0167 0168 QDBusConnection::sessionBus().send(msg); 0169 0170 // get list of currently existing items 0171 msg = QDBusMessage::createMethodCall(WATCHER_SERVICE, "/StatusNotifierWatcher", 0172 "org.freedesktop.DBus.Properties", 0173 "Get"); 0174 0175 msg << "org.kde.StatusNotifierWatcher" << "RegisteredStatusNotifierItems"; 0176 QDBusPendingCall call = QDBusConnection::sessionBus().asyncCall(msg); 0177 QDBusPendingCallWatcher *pendingCallWatcher = new QDBusPendingCallWatcher(call, this); 0178 connect(pendingCallWatcher, &QDBusPendingCallWatcher::finished, this, 0179 [this](QDBusPendingCallWatcher *w) 0180 { 0181 w->deleteLater(); 0182 QDBusPendingReply<QDBusVariant> reply = *w; 0183 QStringList items = reply.value().variant().toStringList(); 0184 for (const QString &item : items) 0185 itemRegistered(item); 0186 } 0187 ); 0188 0189 // connect for new items 0190 QDBusConnection::sessionBus(). 0191 connect(WATCHER_SERVICE, "/StatusNotifierWatcher", 0192 "org.kde.StatusNotifierWatcher", 0193 "StatusNotifierItemRegistered", 0194 this, SLOT(itemRegistered(QString))); 0195 0196 // connect for removed items 0197 QDBusConnection::sessionBus(). 0198 connect(WATCHER_SERVICE, "/StatusNotifierWatcher", 0199 "org.kde.StatusNotifierWatcher", 0200 "StatusNotifierItemUnregistered", 0201 this, SLOT(itemUnregistered(QString))); 0202 } 0203 0204 //-------------------------------------------------------------------------------- 0205 0206 void SysTray::itemRegistered(QString item) 0207 { 0208 //qDebug() << "itemRegistered" << item; 0209 0210 if ( items.contains(item) ) // we have this already (e.g. try kded5 --replace) 0211 return; 0212 0213 int slash = item.indexOf('/'); 0214 if ( slash < 1 ) 0215 return; 0216 0217 QString service = item.left(slash); 0218 QString path = item.mid(slash); 0219 0220 // create item but insert it into the layout just when it was initialized 0221 // since it might be an invalid item which has no StatusNotifierItem path 0222 // and will delete itself on error 0223 SysTrayNotifyItem *sysItem = new SysTrayNotifyItem(this, service, path); 0224 sysItem->setObjectName(item); 0225 sysItem->setFixedSize(QSize(22, 22)); 0226 sysItem->hide(); // until initialized 0227 0228 connect(sysItem, &SysTrayNotifyItem::initialized, this, &SysTray::itemInitialized); 0229 } 0230 0231 //-------------------------------------------------------------------------------- 0232 0233 void SysTray::itemInitialized(SysTrayNotifyItem *item) 0234 { 0235 if ( !items.contains(item->objectName()) ) 0236 items.insert(item->objectName(), item); 0237 0238 arrangeNotifyItems(); // show/hide icons 0239 } 0240 0241 //-------------------------------------------------------------------------------- 0242 0243 void SysTray::itemUnregistered(QString item) 0244 { 0245 //qDebug() << "itemUnregistered" << item; 0246 0247 if ( items.contains(item) ) 0248 { 0249 delete items.take(item); 0250 arrangeNotifyItems(); 0251 } 0252 } 0253 0254 //-------------------------------------------------------------------------------- 0255 0256 void SysTray::arrangeNotifyItems() 0257 { 0258 // cut all items from all layouts 0259 foreach (QHBoxLayout *row, appsRows) 0260 { 0261 while ( QLayoutItem *item = row->itemAt(0) ) 0262 { 0263 if ( item->widget() ) 0264 row->removeWidget(item->widget()); // take widget out but do not delete it 0265 else 0266 delete row->takeAt(0); // spacer items 0267 } 0268 } 0269 0270 // rearrange visible items - "row first" layout 0271 int visibleItems = 0; 0272 foreach (SysTrayNotifyItem *item, items) 0273 if ( item && item->isVisible() ) 0274 visibleItems++; 0275 0276 const int MAX_COLUMNS = static_cast<int>(std::ceil(visibleItems / float(appsRows.count()))); 0277 int row = 0; 0278 foreach (SysTrayNotifyItem *item, items) 0279 { 0280 if ( item && item->isVisible() ) 0281 { 0282 appsRows[row]->addWidget(item, 0, Qt::AlignLeft); 0283 if ( appsRows[row]->count() == MAX_COLUMNS ) 0284 row++; 0285 } 0286 } 0287 0288 foreach (QHBoxLayout *row, appsRows) 0289 row->addStretch(); 0290 } 0291 0292 //-------------------------------------------------------------------------------- 0293 0294 void SysTray::contextMenuEvent(QContextMenuEvent *event) 0295 { 0296 Q_UNUSED(event) 0297 0298 // allow to disable notification popups (e.g. during a presentation) 0299 if ( notificationServer ) 0300 { 0301 QMenu menu; 0302 0303 QAction *action = menu.addAction(i18n("Show New Notifications")); 0304 action->setCheckable(true); 0305 action->setChecked(!notificationServer->getAvoidPopup()); 0306 0307 connect(action, &QAction::triggered, 0308 [this](bool checked) 0309 { 0310 notificationServer->setAvoidPopup(!checked); 0311 }); 0312 0313 menu.exec(QCursor::pos()); 0314 } 0315 } 0316 0317 //--------------------------------------------------------------------------------