File indexing completed on 2024-05-12 05:37:10
0001 /* 0002 SPDX-FileCopyrightText: 2020 Konrad Materka <materka@gmail.com> 0003 0004 SPDX-License-Identifier: GPL-2.0-or-later 0005 */ 0006 0007 #include "systemtraymodel.h" 0008 #include "debug.h" 0009 0010 #include "plasmoidregistry.h" 0011 #include "statusnotifieritemhost.h" 0012 #include "statusnotifieritemsource.h" 0013 #include "systemtraysettings.h" 0014 0015 #include <KLocalizedString> 0016 #include <Plasma/Applet> 0017 #include <Plasma5Support/DataContainer> 0018 #include <PlasmaQuick/AppletQuickItem> 0019 0020 #include <QIcon> 0021 #include <QQuickItem> 0022 0023 BaseModel::BaseModel(QPointer<SystemTraySettings> settings, QObject *parent) 0024 : QAbstractListModel(parent) 0025 , m_settings(settings) 0026 , m_showAllItems(m_settings ? m_settings->isShowAllItems() : true) 0027 , m_shownItems(m_settings ? m_settings->shownItems() : decltype(m_shownItems){}) 0028 , m_hiddenItems(m_settings ? m_settings->hiddenItems() : decltype(m_hiddenItems){}) 0029 { 0030 if (m_settings) { 0031 connect(m_settings, &SystemTraySettings::configurationChanged, this, &BaseModel::onConfigurationChanged); 0032 } 0033 } 0034 0035 QHash<int, QByteArray> BaseModel::roleNames() const 0036 { 0037 return { 0038 {Qt::DisplayRole, QByteArrayLiteral("display")}, 0039 {Qt::DecorationRole, QByteArrayLiteral("decoration")}, 0040 {static_cast<int>(BaseRole::ItemType), QByteArrayLiteral("itemType")}, 0041 {static_cast<int>(BaseRole::ItemId), QByteArrayLiteral("itemId")}, 0042 {static_cast<int>(BaseRole::CanRender), QByteArrayLiteral("canRender")}, 0043 {static_cast<int>(BaseRole::Category), QByteArrayLiteral("category")}, 0044 {static_cast<int>(BaseRole::Status), QByteArrayLiteral("status")}, 0045 {static_cast<int>(BaseRole::EffectiveStatus), QByteArrayLiteral("effectiveStatus")}, 0046 }; 0047 } 0048 0049 void BaseModel::onConfigurationChanged() 0050 { 0051 m_showAllItems = m_settings->isShowAllItems(); 0052 m_shownItems = m_settings->shownItems(); 0053 m_hiddenItems = m_settings->hiddenItems(); 0054 0055 if (rowCount() == 0) { 0056 return; // Avoid assertion 0057 } 0058 0059 Q_EMIT dataChanged(index(0, 0), index(rowCount() - 1, 0), {static_cast<int>(BaseModel::BaseRole::EffectiveStatus)}); 0060 } 0061 0062 Plasma::Types::ItemStatus BaseModel::calculateEffectiveStatus(bool canRender, Plasma::Types::ItemStatus status, QString itemId) const 0063 { 0064 if (!canRender) { 0065 return Plasma::Types::ItemStatus::HiddenStatus; 0066 } 0067 0068 bool forcedShown = m_showAllItems || m_shownItems.contains(itemId); 0069 bool forcedHidden = m_hiddenItems.contains(itemId); 0070 0071 if (!forcedShown && status == Plasma::Types::ItemStatus::HiddenStatus) { 0072 return Plasma::Types::ItemStatus::HiddenStatus; 0073 } else if (forcedShown || (!forcedHidden && status != Plasma::Types::ItemStatus::PassiveStatus)) { 0074 return Plasma::Types::ItemStatus::ActiveStatus; 0075 } else { 0076 return Plasma::Types::ItemStatus::PassiveStatus; 0077 } 0078 } 0079 0080 static QString plasmoidCategoryForMetadata(const KPluginMetaData &metadata) 0081 { 0082 Q_ASSERT(metadata.isValid()); 0083 return metadata.value(QStringLiteral("X-Plasma-NotificationAreaCategory")); 0084 } 0085 0086 PlasmoidModel::PlasmoidModel(const QPointer<SystemTraySettings> &settings, const QPointer<PlasmoidRegistry> &plasmoidRegistry, QObject *parent) 0087 : BaseModel(settings, parent) 0088 , m_plasmoidRegistry(plasmoidRegistry) 0089 { 0090 connect(m_plasmoidRegistry, &PlasmoidRegistry::pluginRegistered, this, &PlasmoidModel::appendRow); 0091 connect(m_plasmoidRegistry, &PlasmoidRegistry::pluginUnregistered, this, &PlasmoidModel::removeRow); 0092 0093 const auto appletMetaDataList = m_plasmoidRegistry->systemTrayApplets(); 0094 for (const auto &info : appletMetaDataList) { 0095 if (!info.isValid() || info.value(QStringLiteral("X-Plasma-NotificationAreaCategory")).isEmpty()) { 0096 continue; 0097 } 0098 appendRow(info); 0099 } 0100 } 0101 0102 QVariant PlasmoidModel::data(const QModelIndex &index, int role) const 0103 { 0104 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { 0105 return QVariant(); 0106 } 0107 0108 const PlasmoidModel::Item &item = m_items[index.row()]; 0109 const KPluginMetaData &pluginMetaData = item.pluginMetaData; 0110 Plasma::Applet *applet = item.applet; 0111 0112 if (role <= Qt::UserRole) { 0113 switch (role) { 0114 case Qt::DisplayRole: { 0115 return pluginMetaData.name(); 0116 } 0117 case Qt::DecorationRole: { 0118 QIcon icon = QIcon::fromTheme(applet ? applet->icon() : "", QIcon::fromTheme(pluginMetaData.iconName())); 0119 return icon.isNull() ? QVariant() : icon; 0120 } 0121 default: 0122 return QVariant(); 0123 } 0124 } 0125 0126 if (role < static_cast<int>(Role::Applet)) { 0127 Plasma::Types::ItemStatus status = Plasma::Types::ItemStatus::UnknownStatus; 0128 if (applet) { 0129 status = applet->status(); 0130 } 0131 0132 switch (static_cast<BaseRole>(role)) { 0133 case BaseRole::ItemType: 0134 return QStringLiteral("Plasmoid"); 0135 case BaseRole::ItemId: 0136 return pluginMetaData.pluginId(); 0137 case BaseRole::CanRender: 0138 return applet != nullptr; 0139 case BaseRole::Category: 0140 return plasmoidCategoryForMetadata(pluginMetaData); 0141 case BaseRole::Status: 0142 return status; 0143 case BaseRole::EffectiveStatus: 0144 return calculateEffectiveStatus(applet != nullptr, status, pluginMetaData.pluginId()); 0145 default: 0146 return QVariant(); 0147 } 0148 } 0149 0150 switch (static_cast<Role>(role)) { 0151 case Role::Applet: 0152 return applet ? QVariant::fromValue(PlasmaQuick::AppletQuickItem::itemForApplet(applet)) : QVariant(); 0153 case Role::HasApplet: 0154 return applet != nullptr; 0155 default: 0156 return QVariant(); 0157 } 0158 } 0159 0160 int PlasmoidModel::rowCount(const QModelIndex &parent) const 0161 { 0162 return parent.isValid() ? 0 : m_items.size(); 0163 } 0164 0165 QHash<int, QByteArray> PlasmoidModel::roleNames() const 0166 { 0167 QHash<int, QByteArray> roles = BaseModel::roleNames(); 0168 0169 roles.insert(static_cast<int>(Role::Applet), QByteArrayLiteral("applet")); 0170 roles.insert(static_cast<int>(Role::HasApplet), QByteArrayLiteral("hasApplet")); 0171 0172 return roles; 0173 } 0174 0175 void PlasmoidModel::addApplet(Plasma::Applet *applet) 0176 { 0177 auto pluginMetaData = applet->pluginMetaData(); 0178 0179 int idx = indexOfPluginId(pluginMetaData.pluginId()); 0180 0181 if (idx < 0) { 0182 idx = rowCount(); 0183 appendRow(pluginMetaData); 0184 } 0185 0186 m_items[idx].applet = applet; 0187 connect(applet, &Plasma::Applet::statusChanged, this, [this, applet](Plasma::Types::ItemStatus status) { 0188 Q_UNUSED(status) 0189 int idx = indexOfPluginId(applet->pluginMetaData().pluginId()); 0190 Q_EMIT dataChanged(index(idx, 0), index(idx, 0), {static_cast<int>(BaseRole::Status)}); 0191 }); 0192 0193 Q_EMIT dataChanged(index(idx, 0), index(idx, 0)); 0194 } 0195 0196 void PlasmoidModel::removeApplet(Plasma::Applet *applet) 0197 { 0198 int idx = indexOfPluginId(applet->pluginMetaData().pluginId()); 0199 if (idx >= 0) { 0200 m_items[idx].applet = nullptr; 0201 Q_EMIT dataChanged(index(idx, 0), index(idx, 0)); 0202 applet->disconnect(this); 0203 } 0204 } 0205 0206 void PlasmoidModel::appendRow(const KPluginMetaData &pluginMetaData) 0207 { 0208 int idx = rowCount(); 0209 beginInsertRows(QModelIndex(), idx, idx); 0210 0211 PlasmoidModel::Item item; 0212 item.pluginMetaData = pluginMetaData; 0213 m_items.append(item); 0214 0215 endInsertRows(); 0216 } 0217 0218 void PlasmoidModel::removeRow(const QString &pluginId) 0219 { 0220 int idx = indexOfPluginId(pluginId); 0221 beginRemoveRows(QModelIndex(), idx, idx); 0222 m_items.removeAt(idx); 0223 endRemoveRows(); 0224 } 0225 0226 int PlasmoidModel::indexOfPluginId(const QString &pluginId) const 0227 { 0228 for (int i = 0; i < rowCount(); i++) { 0229 if (m_items[i].pluginMetaData.pluginId() == pluginId) { 0230 return i; 0231 } 0232 } 0233 return -1; 0234 } 0235 0236 StatusNotifierModel::StatusNotifierModel(QObject *parent) 0237 : BaseModel(nullptr, parent) 0238 { 0239 init(); 0240 } 0241 0242 StatusNotifierModel::StatusNotifierModel(QPointer<SystemTraySettings> settings, QObject *parent) 0243 : BaseModel(settings, parent) 0244 { 0245 init(); 0246 } 0247 0248 static Plasma::Types::ItemStatus extractStatus(const StatusNotifierItemSource *sniData) 0249 { 0250 QString status = sniData->status(); 0251 if (status == QLatin1String("Active")) { 0252 return Plasma::Types::ItemStatus::ActiveStatus; 0253 } else if (status == QLatin1String("NeedsAttention")) { 0254 return Plasma::Types::ItemStatus::NeedsAttentionStatus; 0255 } else if (status == QLatin1String("Passive")) { 0256 return Plasma::Types::ItemStatus::PassiveStatus; 0257 } else { 0258 return Plasma::Types::ItemStatus::UnknownStatus; 0259 } 0260 } 0261 0262 static QVariant extractIcon(const QIcon &icon, const QVariant &defaultValue = QVariant()) 0263 { 0264 if (!icon.isNull()) { 0265 return icon; 0266 } else { 0267 return defaultValue; 0268 } 0269 } 0270 0271 static QString extractItemId(const StatusNotifierItemSource *sniData) 0272 { 0273 const QString itemId = sniData->id(); 0274 // Bug 378910: workaround for Dropbox not following the SNI specification 0275 if (itemId.startsWith(QLatin1String("dropbox-client-"))) { 0276 return QLatin1String("dropbox-client-PID"); 0277 } else { 0278 return itemId; 0279 } 0280 } 0281 0282 QVariant StatusNotifierModel::data(const QModelIndex &index, int role) const 0283 { 0284 if (!checkIndex(index, CheckIndexOption::IndexIsValid)) { 0285 return QVariant(); 0286 } 0287 0288 const StatusNotifierModel::Item &item = m_items[index.row()]; 0289 StatusNotifierItemSource *sniData = m_sniHost->itemForService(item.source); 0290 0291 if (role <= Qt::UserRole) { 0292 switch (role) { 0293 case Qt::DisplayRole: 0294 return sniData->title(); 0295 case Qt::DecorationRole: 0296 return extractIcon(sniData->icon(), sniData->iconName()); 0297 default: 0298 return QVariant(); 0299 } 0300 } 0301 0302 const QString itemId = extractItemId(sniData); 0303 0304 if (role < static_cast<int>(Role::DataEngineSource)) { 0305 switch (static_cast<BaseRole>(role)) { 0306 case BaseRole::ItemType: 0307 return QStringLiteral("StatusNotifier"); 0308 case BaseRole::ItemId: 0309 return itemId; 0310 case BaseRole::CanRender: 0311 return true; 0312 case BaseRole::Category: { 0313 QVariant category = sniData->category(); 0314 return category.isNull() ? QStringLiteral("UnknownCategory") : sniData->category(); 0315 } 0316 case BaseRole::Status: 0317 return extractStatus(sniData); 0318 case BaseRole::EffectiveStatus: 0319 return calculateEffectiveStatus(true, extractStatus(sniData), itemId); 0320 default: 0321 return QVariant(); 0322 } 0323 } 0324 0325 switch (static_cast<Role>(role)) { 0326 case Role::DataEngineSource: 0327 return item.source; 0328 case Role::Service: 0329 return QVariant::fromValue(item.service); 0330 case Role::AttentionIcon: 0331 return extractIcon(sniData->attentionIcon()); 0332 case Role::AttentionIconName: 0333 return sniData->attentionIconName(); 0334 case Role::AttentionMovieName: 0335 return sniData->attentionMovieName(); 0336 case Role::Category: 0337 return sniData->category(); 0338 case Role::Icon: 0339 return extractIcon(sniData->icon()); 0340 case Role::IconName: 0341 return sniData->iconName(); 0342 case Role::IconThemePath: 0343 return sniData->iconThemePath(); 0344 case Role::Id: 0345 return itemId; 0346 case Role::ItemIsMenu: 0347 return sniData->itemIsMenu(); 0348 case Role::OverlayIconName: 0349 return sniData->overlayIconName(); 0350 case Role::Status: 0351 return extractStatus(sniData); 0352 case Role::Title: 0353 return sniData->title(); 0354 case Role::ToolTipSubTitle: 0355 return sniData->toolTipSubTitle(); 0356 case Role::ToolTipTitle: 0357 return sniData->toolTipTitle(); 0358 case Role::WindowId: 0359 return sniData->windowId(); 0360 default: 0361 return QVariant(); 0362 } 0363 } 0364 0365 int StatusNotifierModel::rowCount(const QModelIndex &parent) const 0366 { 0367 return parent.isValid() ? 0 : m_items.size(); 0368 } 0369 0370 QHash<int, QByteArray> StatusNotifierModel::roleNames() const 0371 { 0372 QHash<int, QByteArray> roles = BaseModel::roleNames(); 0373 0374 roles.insert(static_cast<int>(Role::DataEngineSource), QByteArrayLiteral("DataEngineSource")); 0375 roles.insert(static_cast<int>(Role::Service), QByteArrayLiteral("Service")); 0376 roles.insert(static_cast<int>(Role::AttentionIcon), QByteArrayLiteral("AttentionIcon")); 0377 roles.insert(static_cast<int>(Role::AttentionIconName), QByteArrayLiteral("AttentionIconName")); 0378 roles.insert(static_cast<int>(Role::AttentionMovieName), QByteArrayLiteral("AttentionMovieName")); 0379 roles.insert(static_cast<int>(Role::Category), QByteArrayLiteral("Category")); 0380 roles.insert(static_cast<int>(Role::Icon), QByteArrayLiteral("Icon")); 0381 roles.insert(static_cast<int>(Role::IconName), QByteArrayLiteral("IconName")); 0382 roles.insert(static_cast<int>(Role::IconThemePath), QByteArrayLiteral("IconThemePath")); 0383 roles.insert(static_cast<int>(Role::Id), QByteArrayLiteral("Id")); 0384 roles.insert(static_cast<int>(Role::ItemIsMenu), QByteArrayLiteral("ItemIsMenu")); 0385 roles.insert(static_cast<int>(Role::OverlayIconName), QByteArrayLiteral("OverlayIconName")); 0386 roles.insert(static_cast<int>(Role::Status), QByteArrayLiteral("Status")); 0387 roles.insert(static_cast<int>(Role::Title), QByteArrayLiteral("Title")); 0388 roles.insert(static_cast<int>(Role::ToolTipSubTitle), QByteArrayLiteral("ToolTipSubTitle")); 0389 roles.insert(static_cast<int>(Role::ToolTipTitle), QByteArrayLiteral("ToolTipTitle")); 0390 roles.insert(static_cast<int>(Role::WindowId), QByteArrayLiteral("WindowId")); 0391 0392 return roles; 0393 } 0394 0395 void StatusNotifierModel::addSource(const QString &source) 0396 { 0397 int count = rowCount(); 0398 beginInsertRows(QModelIndex(), count, count); 0399 0400 StatusNotifierModel::Item item; 0401 item.source = source; 0402 0403 StatusNotifierItemSource *sni = m_sniHost->itemForService(source); 0404 connect(sni, &StatusNotifierItemSource::dataUpdated, this, [=, this]() { 0405 dataUpdated(source); 0406 }); 0407 item.service = sni->createService(); 0408 m_items.append(item); 0409 endInsertRows(); 0410 } 0411 0412 void StatusNotifierModel::removeSource(const QString &source) 0413 { 0414 int idx = indexOfSource(source); 0415 if (idx >= 0) { 0416 beginRemoveRows(QModelIndex(), idx, idx); 0417 delete m_items[idx].service; 0418 m_items.removeAt(idx); 0419 endRemoveRows(); 0420 } 0421 } 0422 0423 void StatusNotifierModel::dataUpdated(const QString &sourceName) 0424 { 0425 int idx = indexOfSource(sourceName); 0426 0427 if (idx >= 0) { 0428 Q_EMIT dataChanged(index(idx, 0), index(idx, 0)); 0429 } 0430 } 0431 0432 int StatusNotifierModel::indexOfSource(const QString &source) const 0433 { 0434 for (int i = 0; i < rowCount(); i++) { 0435 if (m_items[i].source == source) { 0436 return i; 0437 } 0438 } 0439 return -1; 0440 } 0441 0442 void StatusNotifierModel::init() 0443 { 0444 m_sniHost = StatusNotifierItemHost::self(); 0445 0446 connect(m_sniHost, &StatusNotifierItemHost::itemAdded, this, &StatusNotifierModel::addSource); 0447 connect(m_sniHost, &StatusNotifierItemHost::itemRemoved, this, &StatusNotifierModel::removeSource); 0448 0449 for (const QStringList services = m_sniHost->services(); const QString &service : services) { 0450 addSource(service); 0451 } 0452 } 0453 0454 SystemTrayModel::SystemTrayModel(QObject *parent) 0455 : QConcatenateTablesProxyModel(parent) 0456 { 0457 m_roleNames = QConcatenateTablesProxyModel::roleNames(); 0458 } 0459 0460 QHash<int, QByteArray> SystemTrayModel::roleNames() const 0461 { 0462 return m_roleNames; 0463 } 0464 0465 void SystemTrayModel::addSourceModel(QAbstractItemModel *sourceModel) 0466 { 0467 QHashIterator<int, QByteArray> it(sourceModel->roleNames()); 0468 while (it.hasNext()) { 0469 it.next(); 0470 0471 if (!m_roleNames.contains(it.key())) { 0472 m_roleNames.insert(it.key(), it.value()); 0473 } 0474 } 0475 0476 QConcatenateTablesProxyModel::addSourceModel(sourceModel); 0477 }