Warning, file /pim/kalarm/src/messagenotification.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 * messagenotification.cpp - displays an alarm message in a system notification 0003 * Program: kalarm 0004 * SPDX-FileCopyrightText: 2020-2023 David Jarvie <djarvie@kde.org> 0005 * 0006 * SPDX-License-Identifier: GPL-2.0-or-later 0007 */ 0008 0009 #include "messagenotification.h" 0010 #include "messagedisplayhelper.h" 0011 0012 #include "mainwindow.h" 0013 #include "resourcescalendar.h" 0014 #include "lib/file.h" 0015 #include "kalarm_debug.h" 0016 0017 #include <KAboutData> 0018 #include <KLocalizedString> 0019 #include <KWindowSystem> 0020 #ifdef RESTORE_NOTIFICATIONS 0021 #include <KConfigGroup> 0022 #include <KConfigGui> 0023 #endif 0024 0025 #include <QSessionManager> 0026 0027 using namespace KAlarmCal; 0028 0029 //clazy:excludeall=non-pod-global-static 0030 0031 namespace 0032 { 0033 0034 // Notification eventIds: these are the IDs contained in the '[Event/ID]' 0035 // entries in kalarm.notifyrc. 0036 const QString MessageId = QStringLiteral("Message"); 0037 const QString BeepId = QStringLiteral("MessageBeep"); 0038 const QString SpeakId = QStringLiteral("MessageSpeak"); 0039 const QString ErrorId = QStringLiteral("MessageError"); 0040 0041 const QString NL = QStringLiteral("\n"); 0042 const QString SP = QStringLiteral(" "); 0043 0044 inline QString getNotifyEventId(const KAEvent& event) 0045 { 0046 return event.beep() ? BeepId : event.speak() ? SpeakId : MessageId; 0047 } 0048 0049 } // namespace 0050 0051 0052 /*============================================================================= 0053 * Helper class to save all message notifications' properties on session 0054 * shutdown, to enable them to be recreated on the next startup. 0055 * 0056 * NOTE: When a notification has closed, there is currently no way to know 0057 * whether it has been closed by the user or has timed out. There is also 0058 * no way to know when a notification in the notification history is 0059 * closed by the user. So notifications are not restored on startup, since 0060 * that might re-raise notifications which the user has already closed. 0061 * If this changes in the future, notifications could be restored on 0062 * startup, in the same way as alarm windows are restored. 0063 */ 0064 class MNSessionManager : public QObject 0065 { 0066 Q_OBJECT 0067 public: 0068 MNSessionManager() 0069 { 0070 #ifdef RESTORE_NOTIFICATIONS 0071 connect(qApp, &QGuiApplication::saveStateRequest, this, &MNSessionManager::saveState); 0072 #endif 0073 } 0074 ~MNSessionManager() override {} 0075 0076 static void create() 0077 { 0078 if (!mInstance) 0079 mInstance = new MNSessionManager; 0080 } 0081 0082 private Q_SLOTS: 0083 #ifdef RESTORE_NOTIFICATIONS 0084 /****************************************************************************** 0085 * Called by the session manager to request the application to save its state. 0086 */ 0087 void saveState(QSessionManager& sm) 0088 { 0089 KConfigGui::setSessionConfig(sm.sessionId(), sm.sessionKey()); 0090 KConfig* config = KConfigGui::sessionConfig(); 0091 // Save each MessageNotification's data. 0092 int n = 1; 0093 for (MessageNotification* notif : std::as_const(MessageNotification::mNotificationList)) 0094 { 0095 const QByteArray group = "Notification_" + QByteArray::number(++n); 0096 KConfigGroup cg(config, QLatin1String(group.constData())); 0097 notif->saveProperties(cg); 0098 } 0099 KConfigGroup cg(config, QStringLiteral("Number")); 0100 cg.writeEntry("NumberOfNotifications", MessageNotification::mNotificationList.count()); 0101 } 0102 #endif 0103 0104 private: 0105 static MNSessionManager* mInstance; 0106 }; 0107 0108 MNSessionManager* MNSessionManager::mInstance = nullptr; 0109 0110 QList<MessageNotification*> MessageNotification::mNotificationList; 0111 0112 /****************************************************************************** 0113 * Restore MessageNotification instances saved at session shutdown. 0114 */ 0115 void MessageNotification::sessionRestore() 0116 { 0117 #ifdef RESTORE_NOTIFICATIONS 0118 KConfig* config = KConfigGui::sessionConfig(); 0119 if (config) 0120 { 0121 KConfigGroup cg(config, QStringLiteral("Number")); 0122 const int count = cg.readEntry("NumberOfNotifications", 0); 0123 for (int n = 1; n <= count; ++n) 0124 { 0125 const QByteArray group = "Notification_" + QByteArray::number(n); 0126 cg = KConfigGroup(config, group.constData()); 0127 // Have to initialise the MessageNotification instance with its 0128 // eventId already known. So first create a helper, then read 0129 // its properties, and finally create the MessageNotification. 0130 MessageDisplayHelper* helper = new MessageDisplayHelper(nullptr); 0131 if (!helper->readPropertyValues(cg)) 0132 delete helper; 0133 else 0134 { 0135 const QString notifyId = cg.readEntry("NotifyId"); 0136 new MessageNotification(notifyId, helper); 0137 } 0138 } 0139 } 0140 #endif 0141 } 0142 0143 /****************************************************************************** 0144 * Construct the message notification for the specified alarm. 0145 * Other alarms in the supplied event may have been updated by the caller, so 0146 * the whole event needs to be stored for updating the calendar file when it is 0147 * displayed. 0148 */ 0149 MessageNotification::MessageNotification(const KAEvent& event, const KAAlarm& alarm, int flags) 0150 : KNotification(getNotifyEventId(event)) 0151 , MessageDisplay(event, alarm, flags) 0152 { 0153 qCDebug(KALARM_LOG) << "MessageNotification():" << mEventId(); 0154 MNSessionManager::create(); 0155 if (!(flags & NoInitView)) 0156 MessageNotification::setUpDisplay(); // avoid calling virtual method from constructor 0157 0158 connect(this, &KNotification::closed, this, &MessageNotification::slotClosed); 0159 connect(mHelper, &MessageDisplayHelper::textsChanged, this, &MessageNotification::textsChanged); 0160 connect(mHelper, &MessageDisplayHelper::commandExited, this, &MessageNotification::commandCompleted); 0161 0162 mNotificationList.append(this); 0163 } 0164 0165 /****************************************************************************** 0166 * Construct the message notification for a specified error message. 0167 * If 'dontShowAgain' is non-null, a "Don't show again" option is displayed. Note 0168 * that the option is specific to 'event'. 0169 */ 0170 MessageNotification::MessageNotification(const KAEvent& event, const DateTime& alarmDateTime, 0171 const QStringList& errmsgs, const QString& dontShowAgain) 0172 : KNotification(ErrorId) 0173 , MessageDisplay(event, alarmDateTime, errmsgs, dontShowAgain) 0174 { 0175 qCDebug(KALARM_LOG) << "MessageNotification(errmsg)"; 0176 MNSessionManager::create(); 0177 MessageNotification::setUpDisplay(); // avoid calling virtual method from constructor 0178 0179 connect(this, &KNotification::closed, this, &MessageNotification::slotClosed); 0180 connect(mHelper, &MessageDisplayHelper::textsChanged, this, &MessageNotification::textsChanged); 0181 0182 mNotificationList.append(this); 0183 } 0184 0185 /****************************************************************************** 0186 * Construct the message notification from the properties contained in the 0187 * supplied helper. 0188 * Ownership of the helper is taken by the new instance. 0189 */ 0190 MessageNotification::MessageNotification(const QString& eventId, MessageDisplayHelper* helper) 0191 : KNotification(eventId) 0192 , MessageDisplay(helper) 0193 { 0194 qCDebug(KALARM_LOG) << "MessageNotification(helper):" << mEventId(); 0195 MNSessionManager::create(); 0196 0197 connect(this, &KNotification::closed, this, &MessageNotification::slotClosed); 0198 connect(mHelper, &MessageDisplayHelper::textsChanged, this, &MessageNotification::textsChanged); 0199 connect(mHelper, &MessageDisplayHelper::commandExited, this, &MessageNotification::commandCompleted); 0200 0201 mNotificationList.append(this); 0202 helper->processPropertyValues(); 0203 } 0204 0205 /****************************************************************************** 0206 * Destructor. Perform any post-alarm actions before tidying up. 0207 */ 0208 MessageNotification::~MessageNotification() 0209 { 0210 qCDebug(KALARM_LOG) << "~MessageNotification" << mEventId(); 0211 close(); 0212 mNotificationList.removeAll(this); 0213 } 0214 0215 /****************************************************************************** 0216 * Construct the message notification. 0217 */ 0218 void MessageNotification::setUpDisplay() 0219 { 0220 mHelper->initTexts(); 0221 MessageDisplayHelper::DisplayTexts texts = mHelper->texts(); 0222 0223 setNotificationTitle(texts.title); 0224 0225 // Show the alarm date/time. Any reminder indication is shown in the 0226 // notification title. 0227 // Alarm date/time: display time zone if not local time zone. 0228 mTimeText = texts.time; 0229 0230 mMessageText.clear(); 0231 if (!mErrorWindow()) 0232 { 0233 // It's a normal alarm message notification 0234 switch (mAction()) 0235 { 0236 case KAEvent::SubAction::File: 0237 // Display the file name 0238 mMessageText = texts.fileName + NL; 0239 0240 if (mErrorMsgs().isEmpty()) 0241 { 0242 // Display contents of file 0243 switch (texts.fileType) 0244 { 0245 case File::Type::Image: 0246 break; // can't display an image 0247 case File::Type::TextFormatted: 0248 default: 0249 mMessageText += texts.message; 0250 break; 0251 } 0252 } 0253 break; 0254 0255 case KAEvent::SubAction::Message: 0256 mMessageText = texts.message; 0257 break; 0258 0259 case KAEvent::SubAction::Command: 0260 mMessageText = texts.message; 0261 mCommandInhibit = true; 0262 break; 0263 0264 case KAEvent::SubAction::Email: 0265 default: 0266 break; 0267 } 0268 0269 if (!texts.remainingTime.isEmpty()) 0270 { 0271 // Advance reminder: show remaining time until the actual alarm 0272 mRemainingText = texts.remainingTime; 0273 } 0274 } 0275 else 0276 { 0277 // It's an error message 0278 switch (mAction()) 0279 { 0280 case KAEvent::SubAction::Email: 0281 { 0282 // Display the email addresses and subject. 0283 mMessageText = texts.errorEmail[0] + SP + texts.errorEmail[1] + NL 0284 + texts.errorEmail[2] + SP + texts.errorEmail[3] + NL; 0285 break; 0286 } 0287 case KAEvent::SubAction::Command: 0288 case KAEvent::SubAction::File: 0289 case KAEvent::SubAction::Message: 0290 default: 0291 // Just display the error message strings 0292 break; 0293 } 0294 } 0295 0296 if (!mErrorMsgs().isEmpty()) 0297 { 0298 setIconName(QStringLiteral("dialog-error")); 0299 mMessageText += mErrorMsgs().join(NL); 0300 mCommandInhibit = false; 0301 } 0302 0303 setNotificationText(); 0304 0305 mEnableEdit = mShowEdit(); 0306 if (!mNoDefer()) 0307 { 0308 mEnableDefer = true; 0309 mHelper->setDeferralLimit(mEvent()); // ensure that button is disabled when alarm can't be deferred any more 0310 } 0311 setNotificationButtons(); 0312 0313 mInitialised = true; // the notification's widgets have been created 0314 } 0315 0316 /****************************************************************************** 0317 * Return the number of message notifications. 0318 */ 0319 int MessageNotification::notificationCount() 0320 { 0321 return mNotificationList.count(); 0322 } 0323 0324 /****************************************************************************** 0325 * Returns the widget to act as parent for error messages, etc. 0326 */ 0327 QWidget* MessageNotification::displayParent() 0328 { 0329 return MainWindow::mainMainWindow(); 0330 } 0331 0332 void MessageNotification::closeDisplay() 0333 { 0334 close(); 0335 } 0336 0337 /****************************************************************************** 0338 * Display the notification. 0339 * Output any required audio notification, and reschedule or delete the event 0340 * from the calendar file. 0341 */ 0342 void MessageNotification::showDisplay() 0343 { 0344 if (mInitialised && mHelper->activateAutoClose()) 0345 { 0346 if (!mCommandInhibit && !mShown) 0347 { 0348 qCDebug(KALARM_LOG) << "MessageNotification::showDisplay: sendEvent"; 0349 sendEvent(); 0350 mShown = true; 0351 // Ensure that the screen wakes from sleep, in case the window manager 0352 // doesn't do this when the notification is displayed. 0353 mHelper->wakeScreen(); 0354 } 0355 if (!mDisplayComplete && !mErrorWindow() && mAlarmType() != KAAlarm::Type::Invalid) 0356 mHelper->displayComplete(false); // reschedule 0357 mDisplayComplete = true; 0358 } 0359 } 0360 0361 void MessageNotification::raiseDisplay() 0362 { 0363 } 0364 0365 /****************************************************************************** 0366 * Raise the alarm notification, re-output any required audio notification, and 0367 * reschedule the alarm in the calendar file. 0368 */ 0369 void MessageNotification::repeat(const KAAlarm& alarm) 0370 { 0371 if (!mInitialised) 0372 return; 0373 if (mEventId().isEmpty()) 0374 return; 0375 KAEvent event = ResourcesCalendar::event(mEventId()); 0376 if (event.isValid()) 0377 { 0378 mAlarmType() = alarm.type(); // store new alarm type for use if it is later deferred 0379 if (mHelper->alarmShowing(event)) 0380 ResourcesCalendar::updateEvent(event); 0381 } 0382 } 0383 0384 bool MessageNotification::hasDefer() const 0385 { 0386 return mEnableDefer; 0387 } 0388 0389 /****************************************************************************** 0390 * Show the Defer button when it was previously hidden. 0391 */ 0392 void MessageNotification::showDefer() 0393 { 0394 if (!mEnableDefer) 0395 { 0396 mNoDefer() = false; 0397 mEnableDefer = true; 0398 setNotificationButtons(); 0399 mHelper->setDeferralLimit(mEvent()); // remove button when alarm can't be deferred any more 0400 } 0401 } 0402 0403 /****************************************************************************** 0404 * Convert a reminder notification into a normal alarm notification. 0405 */ 0406 void MessageNotification::cancelReminder(const KAEvent& event, const KAAlarm& alarm) 0407 { 0408 if (mHelper->cancelReminder(event, alarm)) 0409 { 0410 const MessageDisplayHelper::DisplayTexts& texts = mHelper->texts(); 0411 setNotificationTitle(texts.title); 0412 mTimeText = texts.time; 0413 mRemainingText.clear(); 0414 setNotificationText(); 0415 showDefer(); 0416 } 0417 } 0418 0419 /****************************************************************************** 0420 * Update and show the alarm's trigger time. 0421 */ 0422 void MessageNotification::showDateTime(const KAEvent& event, const KAAlarm& alarm) 0423 { 0424 if (mHelper->updateDateTime(event, alarm)) 0425 { 0426 mTimeText = mHelper->texts().time; 0427 setNotificationText(); 0428 } 0429 } 0430 0431 /****************************************************************************** 0432 * Called when the texts to display have changed. 0433 */ 0434 void MessageNotification::textsChanged(MessageDisplayHelper::DisplayTexts::TextIds ids, const QString& change) 0435 { 0436 const MessageDisplayHelper::DisplayTexts& texts = mHelper->texts(); 0437 0438 if (ids & MessageDisplayHelper::DisplayTexts::Title) 0439 setNotificationTitle(texts.title); 0440 0441 bool textChanged = false; 0442 if (ids & MessageDisplayHelper::DisplayTexts::Time) 0443 { 0444 mTimeText = texts.time; 0445 textChanged = true; 0446 } 0447 0448 if (ids & MessageDisplayHelper::DisplayTexts::RemainingTime) 0449 { 0450 mRemainingText = texts.remainingTime; 0451 textChanged = true; 0452 } 0453 0454 if (ids & MessageDisplayHelper::DisplayTexts::MessageAppend) 0455 { 0456 // More output is available from the command which is providing the text 0457 // for this notification. Add the output, but don't show the notification 0458 // until all output has been received. This is a workaround for 0459 // notification texts not being reliably updated by setText(). 0460 mMessageText += change; 0461 return; 0462 } 0463 0464 if (textChanged) 0465 setNotificationText(); 0466 } 0467 0468 /****************************************************************************** 0469 * Called when the command providing the alarm message text has exited. 0470 * Because setText() doesn't reliably update the text in the notification, 0471 * command output notifications are not displayed until all the text is 0472 * available to display. 0473 * 'success' is true if the command did not fail completely. 0474 */ 0475 void MessageNotification::commandCompleted(bool success) 0476 { 0477 qCDebug(KALARM_LOG) << "MessageNotification::commandCompleted:" << success; 0478 if (!success) 0479 { 0480 // The command failed completely. KAlarmApp will output an error 0481 // message, so don't display the empty notification. 0482 deleteLater(); 0483 } 0484 else 0485 { 0486 // The command may have produced some output, so display that, although 0487 // if an error occurred, KAlarmApp might display an error message as 0488 // well. 0489 setNotificationText(); 0490 mCommandInhibit = false; 0491 showDisplay(); 0492 } 0493 } 0494 0495 /****************************************************************************** 0496 * Set the notification's title. 0497 */ 0498 void MessageNotification::setNotificationTitle(const QString& text) 0499 { 0500 setTitle(mErrorMsgs().isEmpty() ? QString() : text); 0501 } 0502 0503 /****************************************************************************** 0504 * Set the notification's text by combining the text portions. 0505 */ 0506 void MessageNotification::setNotificationText() 0507 { 0508 setText(mMessageText + NL + mTimeText + NL + QStringLiteral("<i>") + mRemainingText + QStringLiteral("</i>")); 0509 } 0510 0511 /****************************************************************************** 0512 * Set the notification's action buttons. 0513 */ 0514 void MessageNotification::setNotificationButtons() 0515 { 0516 if (mEnableEdit) 0517 { 0518 auto editAction = addAction(i18nc("@action:button", "Edit")); 0519 connect(editAction, &KNotificationAction::activated, this, [this] { 0520 if (mHelper->createEdit()) 0521 mHelper->executeEdit(); 0522 }); 0523 } 0524 0525 if (mEnableDefer) 0526 { 0527 auto deferAction = addAction(i18nc("@action:button", "Defer")); 0528 connect(deferAction, &KNotificationAction::activated, this, [this] { 0529 DeferDlgData* data = createDeferDlg(this, true); 0530 executeDeferDlg(data); 0531 }); 0532 } 0533 0534 auto defaultAction = addDefaultAction(KAboutData::applicationData().displayName()); 0535 connect(defaultAction, &KNotificationAction::activated, this, &MessageNotification::slotDefaultActivated); 0536 } 0537 0538 bool MessageNotification::isDeferButtonEnabled() const 0539 { 0540 return mEnableDefer; 0541 } 0542 0543 void MessageNotification::enableDeferButton(bool enable) 0544 { 0545 mEnableDefer = enable; 0546 setNotificationButtons(); 0547 } 0548 0549 void MessageNotification::enableEditButton(bool enable) 0550 { 0551 mEnableEdit = enable; 0552 setNotificationButtons(); 0553 } 0554 0555 /****************************************************************************** 0556 * Save settings to the session managed config file, for restoration 0557 * when the program is restored. 0558 */ 0559 void MessageNotification::saveProperties(KConfigGroup& config) 0560 { 0561 Q_UNUSED(config) 0562 #ifdef RESTORE_NOTIFICATIONS 0563 if (mDisplayComplete && mHelper->saveProperties(config)) 0564 config.writeEntry("NotifyId", eventId()); 0565 #endif 0566 } 0567 0568 /****************************************************************************** 0569 * Called when the default action in the notification has been pressed, to show 0570 * the main window. 0571 */ 0572 void MessageNotification::slotDefaultActivated() 0573 { 0574 KWindowSystem::setCurrentXdgActivationToken(xdgActivationToken()); 0575 displayMainWindow(); 0576 } 0577 0578 /****************************************************************************** 0579 * Called when the notification has closed, either by user action of by timeout. 0580 * Note that when a notification has timed out, it shows in the notification 0581 * history, but there is no way to know if the user closes it there. 0582 * Only quits the application if there is no system tray icon displayed. 0583 */ 0584 void MessageNotification::slotClosed() 0585 { 0586 qCDebug(KALARM_LOG) << "MessageNotification::slotClosed"; 0587 mHelper->closeEvent(); 0588 } 0589 0590 #include "messagenotification.moc" 0591 #include "moc_messagenotification.cpp" 0592 0593 // vim: et sw=4: