File indexing completed on 2024-05-19 05:38:22
0001 /* 0002 SPDX-FileCopyrightText: 2019 Kai Uwe Broulik <kde@privat.broulik.de> 0003 0004 SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL 0005 */ 0006 0007 #include "kcm.h" 0008 0009 #include <QAction> 0010 #include <QCommandLineParser> 0011 #include <QDialog> 0012 #include <QDialogButtonBox> 0013 #include <QFile> 0014 #include <QFileInfo> 0015 #include <QPushButton> 0016 #include <QQuickItem> 0017 #include <QQuickRenderControl> 0018 #include <QQuickWindow> 0019 #include <QStandardPaths> 0020 #include <QVBoxLayout> 0021 #include <QWindow> 0022 0023 #include <KConfigGroup> 0024 #include <KGlobalAccel> 0025 #include <KLocalizedString> 0026 #include <KPluginFactory> 0027 0028 #include <algorithm> 0029 0030 #include "kcm_notifications_debug.h" 0031 #include "notificationsdata.h" 0032 #include "soundthemeconfig.h" 0033 0034 #include <libnotificationmanager/badgesettings.h> 0035 #include <libnotificationmanager/behaviorsettings.h> 0036 #include <libnotificationmanager/donotdisturbsettings.h> 0037 #include <libnotificationmanager/jobsettings.h> 0038 #include <libnotificationmanager/notificationsettings.h> 0039 0040 #include <canberra.h> 0041 0042 K_PLUGIN_FACTORY_WITH_JSON(KCMNotificationsFactory, "kcm_notifications.json", registerPlugin<KCMNotifications>(); registerPlugin<NotificationsData>();) 0043 0044 KCMNotifications::KCMNotifications(QObject *parent, const KPluginMetaData &data, const QVariantList &args) 0045 : KQuickManagedConfigModule(parent, data) 0046 , m_sourcesModel(new SourcesModel(this)) 0047 , m_filteredModel(new FilterProxyModel(this)) 0048 , m_eventsModel(new EventsProxyModel(this)) 0049 , m_data(new NotificationsData(this)) 0050 , m_toggleDoNotDisturbAction(new QAction(this)) 0051 , m_soundThemeConfig(new SoundThemeConfig(this)) 0052 { 0053 const char uri[] = "org.kde.private.kcms.notifications"; 0054 qmlRegisterUncreatableType<SourcesModel>(uri, 1, 0, "SourcesModel", QStringLiteral("Cannot create instances of SourcesModel")); 0055 0056 qmlRegisterAnonymousType<FilterProxyModel>("FilterProxyModel", 1); 0057 qmlRegisterAnonymousType<EventsProxyModel>("EventsProxyModel", 1); 0058 qmlRegisterAnonymousType<QKeySequence>("QKeySequence", 1); 0059 qmlRegisterAnonymousType<NotificationManager::DoNotDisturbSettings>("DoNotDisturbSettings", 1); 0060 qmlRegisterAnonymousType<NotificationManager::NotificationSettings>("NotificationSettings", 1); 0061 qmlRegisterAnonymousType<NotificationManager::JobSettings>("JobSettings", 1); 0062 qmlRegisterAnonymousType<NotificationManager::BadgeSettings>("BadgeSettings", 1); 0063 qmlRegisterAnonymousType<NotificationManager::BehaviorSettings>("BehaviorSettings", 1); 0064 qmlProtectModule(uri, 1); 0065 0066 m_filteredModel->setSourceModel(m_sourcesModel); 0067 m_eventsModel->setSourceModel(m_sourcesModel); 0068 0069 // for KGlobalAccel... 0070 // keep in sync with globalshortcuts.cpp in notification plasmoid! 0071 m_toggleDoNotDisturbAction->setObjectName(QStringLiteral("toggle do not disturb")); 0072 m_toggleDoNotDisturbAction->setProperty("componentName", QStringLiteral("plasmashell")); 0073 m_toggleDoNotDisturbAction->setText(i18n("Toggle do not disturb")); 0074 m_toggleDoNotDisturbAction->setIcon(QIcon::fromTheme(QStringLiteral("notifications-disabled"))); 0075 0076 QStringList stringArgs; 0077 stringArgs.reserve(args.count() + 1); 0078 // need to add a fake argv[0] for QCommandLineParser 0079 stringArgs.append(QStringLiteral("kcm_notifications")); 0080 for (const QVariant &arg : args) { 0081 stringArgs.append(arg.toString()); 0082 } 0083 0084 QCommandLineParser parser; 0085 0086 QCommandLineOption desktopEntryOption(QStringLiteral("desktop-entry"), QString(), QStringLiteral("desktop-entry")); 0087 parser.addOption(desktopEntryOption); 0088 QCommandLineOption notifyRcNameOption(QStringLiteral("notifyrc"), QString(), QStringLiteral("notifyrcname")); 0089 parser.addOption(notifyRcNameOption); 0090 QCommandLineOption eventIdOption(QStringLiteral("event-id"), QString(), QStringLiteral("event-id")); 0091 parser.addOption(eventIdOption); 0092 0093 parser.parse(stringArgs); 0094 0095 setInitialDesktopEntry(parser.value(desktopEntryOption)); 0096 setInitialNotifyRcName(parser.value(notifyRcNameOption)); 0097 setInitialEventId(parser.value(eventIdOption)); 0098 0099 connect(this, &KCMNotifications::toggleDoNotDisturbShortcutChanged, this, &KCMNotifications::settingsChanged); 0100 connect(m_sourcesModel, &QAbstractItemModel::dataChanged, this, &KCMNotifications::settingsChanged); 0101 connect(this, &KCMNotifications::defaultsIndicatorsVisibleChanged, this, &KCMNotifications::onDefaultsIndicatorsVisibleChanged); 0102 } 0103 0104 KCMNotifications::~KCMNotifications() 0105 { 0106 } 0107 0108 SourcesModel *KCMNotifications::sourcesModel() const 0109 { 0110 return m_sourcesModel; 0111 } 0112 0113 FilterProxyModel *KCMNotifications::filteredModel() const 0114 { 0115 return m_filteredModel; 0116 } 0117 0118 EventsProxyModel *KCMNotifications::eventsModel() const 0119 { 0120 return m_eventsModel; 0121 } 0122 0123 NotificationManager::DoNotDisturbSettings *KCMNotifications::dndSettings() const 0124 { 0125 return m_data->dndSettings(); 0126 } 0127 0128 NotificationManager::NotificationSettings *KCMNotifications::notificationSettings() const 0129 { 0130 return m_data->notificationSettings(); 0131 } 0132 0133 NotificationManager::JobSettings *KCMNotifications::jobSettings() const 0134 { 0135 return m_data->jobSettings(); 0136 } 0137 0138 NotificationManager::BadgeSettings *KCMNotifications::badgeSettings() const 0139 { 0140 return m_data->badgeSettings(); 0141 } 0142 0143 QKeySequence KCMNotifications::toggleDoNotDisturbShortcut() const 0144 { 0145 return m_toggleDoNotDisturbShortcut; 0146 } 0147 0148 void KCMNotifications::setToggleDoNotDisturbShortcut(const QKeySequence &shortcut) 0149 { 0150 if (m_toggleDoNotDisturbShortcut == shortcut) { 0151 return; 0152 } 0153 0154 m_toggleDoNotDisturbShortcut = shortcut; 0155 m_toggleDoNotDisturbShortcutDirty = true; 0156 Q_EMIT toggleDoNotDisturbShortcutChanged(); 0157 } 0158 0159 QString KCMNotifications::initialDesktopEntry() const 0160 { 0161 return m_initialDesktopEntry; 0162 } 0163 0164 void KCMNotifications::setInitialDesktopEntry(const QString &desktopEntry) 0165 { 0166 if (m_initialDesktopEntry != desktopEntry) { 0167 m_initialDesktopEntry = desktopEntry; 0168 Q_EMIT initialDesktopEntryChanged(); 0169 } 0170 } 0171 0172 QString KCMNotifications::initialNotifyRcName() const 0173 { 0174 return m_initialNotifyRcName; 0175 } 0176 0177 void KCMNotifications::setInitialNotifyRcName(const QString ¬ifyRcName) 0178 { 0179 if (m_initialNotifyRcName != notifyRcName) { 0180 m_initialNotifyRcName = notifyRcName; 0181 Q_EMIT initialNotifyRcNameChanged(); 0182 } 0183 } 0184 0185 QString KCMNotifications::initialEventId() const 0186 { 0187 return m_initialEventId; 0188 } 0189 0190 void KCMNotifications::setInitialEventId(const QString &eventId) 0191 { 0192 if (m_initialEventId != eventId) { 0193 m_initialEventId = eventId; 0194 Q_EMIT initialEventIdChanged(); 0195 } 0196 } 0197 0198 NotificationManager::BehaviorSettings *KCMNotifications::behaviorSettings(const QModelIndex &index) 0199 { 0200 if (!index.isValid()) { 0201 return nullptr; 0202 } 0203 return m_data->behaviorSettings(index.row()); 0204 } 0205 0206 bool KCMNotifications::isDefaultsBehaviorSettings() const 0207 { 0208 return m_data->isDefaultsBehaviorSettings(); 0209 } 0210 0211 void KCMNotifications::load() 0212 { 0213 KQuickManagedConfigModule::load(); 0214 0215 bool firstLoad = m_firstLoad; 0216 if (m_firstLoad) { 0217 m_firstLoad = false; 0218 m_sourcesModel->load(); 0219 0220 for (int i = 0; i < m_sourcesModel->rowCount(); ++i) { 0221 const QModelIndex index = m_sourcesModel->index(i, 0); 0222 if (!index.isValid()) { 0223 continue; 0224 } 0225 0226 QString typeName; 0227 QString groupName; 0228 if (m_sourcesModel->data(index, SourcesModel::SourceTypeRole) == SourcesModel::ApplicationType) { 0229 typeName = QStringLiteral("Applications"); 0230 groupName = m_sourcesModel->data(index, SourcesModel::DesktopEntryRole).toString(); 0231 } else { 0232 typeName = QStringLiteral("Services"); 0233 groupName = m_sourcesModel->data(index, SourcesModel::NotifyRcNameRole).toString(); 0234 } 0235 auto *toAdd = new NotificationManager::BehaviorSettings(typeName, groupName, this); 0236 m_data->insertBehaviorSettings(index.row(), toAdd); 0237 createConnections(toAdd, index); 0238 } 0239 } 0240 0241 m_sourcesModel->loadEvents(); 0242 m_data->loadBehaviorSettings(); 0243 0244 const QKeySequence toggleDoNotDisturbShortcut = 0245 KGlobalAccel::self() 0246 ->globalShortcut(m_toggleDoNotDisturbAction->property("componentName").toString(), m_toggleDoNotDisturbAction->objectName()) 0247 .value(0); 0248 0249 if (m_toggleDoNotDisturbShortcut != toggleDoNotDisturbShortcut) { 0250 m_toggleDoNotDisturbShortcut = toggleDoNotDisturbShortcut; 0251 Q_EMIT toggleDoNotDisturbShortcutChanged(); 0252 } 0253 0254 m_toggleDoNotDisturbShortcutDirty = false; 0255 if (firstLoad) { 0256 Q_EMIT firstLoadDone(); 0257 } 0258 } 0259 0260 void KCMNotifications::save() 0261 { 0262 KQuickManagedConfigModule::save(); 0263 m_data->saveBehaviorSettings(); 0264 m_sourcesModel->saveEvents(); 0265 0266 if (m_toggleDoNotDisturbShortcutDirty) { 0267 // KeySequenceItem will already have checked whether the shortcut is available 0268 KGlobalAccel::self()->setShortcut(m_toggleDoNotDisturbAction, {m_toggleDoNotDisturbShortcut}, KGlobalAccel::NoAutoloading); 0269 } 0270 } 0271 0272 void KCMNotifications::defaults() 0273 { 0274 KQuickManagedConfigModule::defaults(); 0275 m_data->defaultsBehaviorSettings(); 0276 m_sourcesModel->setEventDefaults(); 0277 0278 setToggleDoNotDisturbShortcut(QKeySequence()); 0279 } 0280 0281 void KCMNotifications::onDefaultsIndicatorsVisibleChanged() 0282 { 0283 for (int i = 0; i < m_sourcesModel->rowCount(); ++i) { 0284 const QModelIndex index = m_sourcesModel->index(i, 0); 0285 updateModelIsDefaultStatus(index); 0286 } 0287 } 0288 0289 void KCMNotifications::updateModelIsDefaultStatus(const QModelIndex &index) 0290 { 0291 if (index.isValid()) { 0292 m_sourcesModel->setData(index, behaviorSettings(index)->isDefaults(), SourcesModel::IsDefaultRole); 0293 Q_EMIT isDefaultsBehaviorSettingsChanged(); 0294 } 0295 } 0296 0297 bool KCMNotifications::isSaveNeeded() const 0298 { 0299 return m_toggleDoNotDisturbShortcutDirty || m_data->isSaveNeededBehaviorSettings() || m_sourcesModel->isEventSaveNeeded(); 0300 } 0301 0302 bool KCMNotifications::isDefaults() const 0303 { 0304 return m_data->isDefaultsBehaviorSettings(); 0305 } 0306 0307 void KCMNotifications::createConnections(NotificationManager::BehaviorSettings *settings, const QModelIndex &index) 0308 { 0309 connect(settings, &NotificationManager::BehaviorSettings::ShowPopupsChanged, this, &KCMNotifications::settingsChanged); 0310 connect(settings, &NotificationManager::BehaviorSettings::ShowPopupsInDndModeChanged, this, &KCMNotifications::settingsChanged); 0311 connect(settings, &NotificationManager::BehaviorSettings::ShowInHistoryChanged, this, &KCMNotifications::settingsChanged); 0312 connect(settings, &NotificationManager::BehaviorSettings::ShowBadgesChanged, this, &KCMNotifications::settingsChanged); 0313 0314 connect(settings, &NotificationManager::BehaviorSettings::ShowPopupsChanged, this, [this, index] { 0315 updateModelIsDefaultStatus(index); 0316 }); 0317 connect(settings, &NotificationManager::BehaviorSettings::ShowPopupsInDndModeChanged, this, [this, index] { 0318 updateModelIsDefaultStatus(index); 0319 }); 0320 connect(settings, &NotificationManager::BehaviorSettings::ShowInHistoryChanged, this, [this, index] { 0321 updateModelIsDefaultStatus(index); 0322 }); 0323 connect(settings, &NotificationManager::BehaviorSettings::ShowBadgesChanged, this, [this, index] { 0324 updateModelIsDefaultStatus(index); 0325 }); 0326 } 0327 0328 QUrl KCMNotifications::soundsLocation() 0329 { 0330 const QString soundsPath = QStandardPaths::locateAll(QStandardPaths::GenericDataLocation, QStringLiteral("sounds"), QStandardPaths::LocateDirectory).last(); 0331 return QUrl::fromLocalFile(soundsPath); 0332 } 0333 0334 void KCMNotifications::playSound(const QString &soundName) 0335 { 0336 // Legacy implementation. Fallback lookup for a full path within the `$XDG_DATA_LOCATION/sounds` dirs 0337 QUrl fallbackUrl; 0338 const auto dataLocations = QStandardPaths::standardLocations(QStandardPaths::GenericDataLocation); 0339 for (const QString &dataLocation : dataLocations) { 0340 fallbackUrl = QUrl::fromUserInput(soundName, dataLocation + QStringLiteral("/sounds"), QUrl::AssumeLocalFile); 0341 if (fallbackUrl.isLocalFile() && QFileInfo::exists(fallbackUrl.toLocalFile())) { 0342 break; 0343 } else if (!fallbackUrl.isLocalFile() && fallbackUrl.isValid()) { 0344 break; 0345 } 0346 fallbackUrl.clear(); 0347 } 0348 0349 if (!m_canberraContext) { 0350 int ret = ca_context_create(&m_canberraContext); 0351 if (ret != CA_SUCCESS) { 0352 qCWarning(KCM_NOTIFICATIONS) << "Failed to initialize canberra context for audio notification:" << ca_strerror(ret); 0353 m_canberraContext = nullptr; 0354 return; 0355 } 0356 0357 // clang-format off 0358 ret = ca_context_change_props(m_canberraContext, 0359 CA_PROP_APPLICATION_NAME, qUtf8Printable(metaData().name()), 0360 CA_PROP_APPLICATION_ID, qUtf8Printable(metaData().pluginId()), 0361 CA_PROP_APPLICATION_ICON_NAME, qUtf8Printable(metaData().iconName()), 0362 nullptr); 0363 // clang-format on 0364 if (ret != CA_SUCCESS) { 0365 qCWarning(KCM_NOTIFICATIONS) << "Failed to set application properties on canberra context for audio notification:" << ca_strerror(ret); 0366 } 0367 } 0368 0369 ca_proplist *props = nullptr; 0370 ca_proplist_create(&props); 0371 0372 ca_proplist_sets(props, CA_PROP_EVENT_ID, soundName.toLatin1().constData()); 0373 ca_proplist_sets(props, CA_PROP_CANBERRA_XDG_THEME_NAME, m_soundThemeConfig->soundTheme().toLatin1().constData()); 0374 if (!fallbackUrl.isEmpty()) { 0375 ca_proplist_sets(props, CA_PROP_MEDIA_FILENAME, QFile::encodeName(fallbackUrl.toLocalFile()).constData()); 0376 } 0377 0378 // We'll also want this cached for a time. volatile makes sure the cache is 0379 // dropped after some time or when the cache is under pressure. 0380 ca_proplist_sets(props, CA_PROP_CANBERRA_CACHE_CONTROL, "volatile"); 0381 0382 int ret = ca_context_play_full(m_canberraContext, 0, props, nullptr, nullptr); 0383 0384 ca_proplist_destroy(props); 0385 0386 if (ret != CA_SUCCESS) { 0387 qCWarning(KCM_NOTIFICATIONS) << "Failed to play sound" << soundName << "with canberra:" << ca_strerror(ret); 0388 return; 0389 } 0390 } 0391 0392 #include "kcm.moc"