[pim/kalarm] /: Don't execute display alarms while desktop notifications are inhibited

David Jarvie null at kde.org
Thu Oct 22 00:48:54 BST 2020


Git commit 9e8e3f63c0243e04009414701efb541ab196fc2b by David Jarvie.
Committed on 21/10/2020 at 23:45.
Pushed by djarvie into branch 'master'.

Don't execute display alarms while desktop notifications are inhibited

M  +2    -1    Changelog
M  +6    -1    doc/index.docbook
M  +3    -0    src/CMakeLists.txt
A  +28   -0    src/data/org.freedesktop.DBus.Properties.xml
A  +60   -0    src/data/org.freedesktop.Notifications.xml
M  +1    -1    src/editdlg.cpp
M  +94   -21   src/kalarmapp.cpp
M  +7    -2    src/kalarmapp.h
M  +62   -17   src/resourcescalendar.cpp
M  +5    -3    src/resourcescalendar.h

https://invent.kde.org/pim/kalarm/commit/9e8e3f63c0243e04009414701efb541ab196fc2b

diff --git a/Changelog b/Changelog
index cead132b..42d5e2db 100644
--- a/Changelog
+++ b/Changelog
@@ -1,7 +1,8 @@
 KAlarm Change Log
 
-=== Version 3.1.0 (KDE Applications 20.12) --- 24 September 2020 ===
+=== Version 3.1.0 (KDE Applications 20.12) --- 22 October 2020 ===
 + Add option to show alarm message as a notification instead of in a window [KDE Bug 345922]
++ Don't execute display alarms while desktop notifications are inhibited.
 
 === Version 3.0.3 (KDE Applications 20.08.3) --- 15 October 2020 ===
 + Prevent resources being disabled at logout [KDE Bug 427722]
diff --git a/doc/index.docbook b/doc/index.docbook
index ed86cc44..663e673b 100644
--- a/doc/index.docbook
+++ b/doc/index.docbook
@@ -39,7 +39,7 @@
 
 <!-- Don't change format of date and version of the documentation -->
 
-<date>2020-09-24</date>
+<date>2020-10-22</date>
 <releaseinfo>3.1.0 (Applications 20.12)</releaseinfo>
 
 <abstract>
@@ -1926,6 +1926,11 @@ depending on the option chosen in the
 <link linkend="alarm-edit-dlg">Alarm Edit dialog</link>. These two
 display options are described below.</para>
 
+<para>If you inhibit notifications for the desktop, the execution of
+display alarms will be suspended until notifications are re-enabled.
+Note that this applies to all display alarms, regardless of whether
+they are displayed in windows or as notifications.</para>
+
 <sect2 id="message-window">
 <title>Alarm Message Window</title>
 
diff --git a/src/CMakeLists.txt b/src/CMakeLists.txt
index 96e028b5..821b5058 100644
--- a/src/CMakeLists.txt
+++ b/src/CMakeLists.txt
@@ -145,6 +145,9 @@ qt5_add_dbus_adaptor(kalarm_bin_SRCS data/org.kde.kalarm.kalarm.xml dbushandler.
 
 qt5_add_dbus_interfaces(kalarm_bin_SRCS data/org.kde.kmail.kmail.xml)
 
+qt5_add_dbus_interface(kalarm_bin_SRCS data/org.freedesktop.Notifications.xml notifications_interface)
+qt5_add_dbus_interface(kalarm_bin_SRCS data/org.freedesktop.DBus.Properties.xml dbusproperties)
+
 kcfg_generate_dbus_interface(${CMAKE_CURRENT_SOURCE_DIR}/data/kalarmresource.kcfg org.kde.Akonadi.KAlarm.Settings)
 qt5_add_dbus_interface(kalarm_bin_SRCS ${CMAKE_CURRENT_BINARY_DIR}/org.kde.Akonadi.KAlarm.Settings.xml kalarmsettings KAlarmSettings)
 
diff --git a/src/data/org.freedesktop.DBus.Properties.xml b/src/data/org.freedesktop.DBus.Properties.xml
new file mode 100644
index 00000000..688ed108
--- /dev/null
+++ b/src/data/org.freedesktop.DBus.Properties.xml
@@ -0,0 +1,28 @@
+<?xml version="1.0"?>
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<!-- GDBus 2.32.4 -->
+<node>
+  <interface name="org.freedesktop.DBus.Properties">
+    <method name="Get">
+      <arg type="s" name="interface_name" direction="in"/>
+      <arg type="s" name="property_name" direction="in"/>
+      <arg type="v" name="value" direction="out"/>
+    </method>
+    <method name="GetAll">
+      <arg type="s" name="interface_name" direction="in"/>
+      <arg type="a{sv}" name="properties" direction="out"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QVariantMap" />
+    </method>
+    <method name="Set">
+      <arg type="s" name="interface_name" direction="in"/>
+      <arg type="s" name="property_name" direction="in"/>
+      <arg type="v" name="value" direction="in"/>
+    </method>
+    <signal name="PropertiesChanged">
+      <arg type="s" name="interface_name"/>
+      <arg type="a{sv}" name="changed_properties"/>
+      <arg type="as" name="invalidated_properties"/>
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out1" value="QVariantMap" />
+    </signal>
+  </interface>
+</node>
diff --git a/src/data/org.freedesktop.Notifications.xml b/src/data/org.freedesktop.Notifications.xml
new file mode 100644
index 00000000..fc0825df
--- /dev/null
+++ b/src/data/org.freedesktop.Notifications.xml
@@ -0,0 +1,60 @@
+<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
+<node>
+  <interface name="org.freedesktop.Notifications">
+    <signal name="NotificationClosed">
+      <arg name="id" type="u" direction="out"/>
+      <arg name="reason" type="u" direction="out"/>
+    </signal>
+    <signal name="ActionInvoked">
+      <arg name="id" type="u" direction="out"/>
+      <arg name="action_key" type="s" direction="out"/>
+    </signal>
+    <method name="Notify">
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In6" value="QVariantMap"/>
+      <arg type="u" direction="out"/>
+      <arg name="app_name" type="s" direction="in"/>
+      <arg name="replaces_id" type="u" direction="in"/>
+      <arg name="app_icon" type="s" direction="in"/>
+      <arg name="summary" type="s" direction="in"/>
+      <arg name="body" type="s" direction="in"/>
+      <arg name="actions" type="as" direction="in"/>
+      <arg name="hints" type="a{sv}" direction="in"/>
+      <arg name="timeout" type="i" direction="in"/>
+    </method>
+    <method name="CloseNotification">
+      <arg name="id" type="u" direction="in"/>
+    </method>
+    <method name="GetCapabilities">
+      <arg type="as" name="caps" direction="out"/>
+    </method>
+    <method name="GetServerInformation">
+      <arg type="s" name="name" direction="out"/>
+      <arg type="s" name="vendor" direction="out"/>
+      <arg type="s" name="version" direction="out"/>
+      <arg type="s" name="spec_version" direction="out"/>
+    </method>
+
+    <!-- Inhibitions -->
+    <method name="Inhibit">
+      <annotation name="org.qtproject.QtDBus.QtTypeName.In2" value="QVariantMap"/>
+      <arg type="u" direction="out"/>
+      <arg name="desktop_entry" type="s" direction="in"/>
+      <arg name="reason" type="s" direction="in"/>
+      <arg name="hints" type="a{sv}" direction="in"/>
+    </method>
+
+    <method name="UnInhibit">
+      <arg type="u" direction="in"/>
+    </method>
+
+    <property name="Inhibited" type="b" access="read">
+      <annotation name="org.freedesktop.DBus.Property.EmitsChangedSignal" value="true"/>
+    </property>
+
+    <!--<method name="ListInhibitors">
+      <annotation name="org.qtproject.QtDBus.QtTypeName.Out0" value="QList<Inhibition>"/>
+      <arg name="inhibitors" type="a(ssa{sv})" direction="out"/>
+    </method>-->
+
+  </interface>
+</node>
diff --git a/src/editdlg.cpp b/src/editdlg.cpp
index cb73bde0..48fdf076 100644
--- a/src/editdlg.cpp
+++ b/src/editdlg.cpp
@@ -1166,7 +1166,7 @@ void EditAlarmDlg::slotTry()
             event.setEventId(mEventId);
         }
         type_aboutToTry();
-        void* result = theApp()->execAlarm(event, event.firstAlarm(), KAlarmApp::NoRecordCmdError);
+        void* result = theApp()->execAlarm(event, event.firstAlarm(), KAlarmApp::NoRecordCmdError | KAlarmApp::NoNotifyInhibit);
         type_executedTry(text, result);
     }
 }
diff --git a/src/kalarmapp.cpp b/src/kalarmapp.cpp
index ac03d923..407e6f81 100644
--- a/src/kalarmapp.cpp
+++ b/src/kalarmapp.cpp
@@ -30,6 +30,8 @@
 #include "lib/desktop.h"
 #include "lib/messagebox.h"
 #include "lib/shellprocess.h"
+#include "notifications_interface.h" // DBUS-generated
+#include "dbusproperties.h"          // DBUS-generated
 #include "kalarm_debug.h"
 
 #include <KAlarmCal/DateTime>
@@ -62,6 +64,9 @@ namespace
 {
 const int RESOURCES_TIMEOUT = 30;   // timeout (seconds) for resources to be populated
 
+const char FDO_NOTIFICATIONS_SERVICE[] = "org.freedesktop.Notifications";
+const char FDO_NOTIFICATIONS_PATH[]    = "/org/freedesktop/Notifications";
+
 /******************************************************************************
 * Find the maximum number of seconds late which a late-cancel alarm is allowed
 * to be. This is calculated as the late cancel interval, plus a few seconds
@@ -194,6 +199,23 @@ void KAlarmApp::initialise()
         DateTime::setStartOfDay(Preferences::startOfDay());
         mPrefsArchivedColour = Preferences::archivedColour();
     }
+
+    // Get notified when the Freedesktop notifications properties have changed.
+    QDBusConnection conn = QDBusConnection::sessionBus();
+    if (conn.interface()->isServiceRegistered(QString::fromLatin1(FDO_NOTIFICATIONS_SERVICE)))
+    {
+        OrgFreedesktopDBusPropertiesInterface* piface = new OrgFreedesktopDBusPropertiesInterface(
+                QString::fromLatin1(FDO_NOTIFICATIONS_SERVICE),
+                QString::fromLatin1(FDO_NOTIFICATIONS_PATH),
+                conn, this);
+        connect(piface, &OrgFreedesktopDBusPropertiesInterface::PropertiesChanged,
+                this, &KAlarmApp::slotFDOPropertiesChanged);
+        OrgFreedesktopNotificationsInterface niface(
+                QString::fromLatin1(FDO_NOTIFICATIONS_SERVICE),
+                QString::fromLatin1(FDO_NOTIFICATIONS_PATH),
+                conn);
+        mNotificationsInhibited = niface.inhibited();
+    }
 }
 
 /******************************************************************************
@@ -845,7 +867,7 @@ void KAlarmApp::checkNextDueAlarm()
         return;
     // Find the first alarm due
     KADateTime nextDt;
-    const KAEvent nextEvent = ResourcesCalendar::earliestAlarm(nextDt);
+    const KAEvent nextEvent = ResourcesCalendar::earliestAlarm(nextDt, mNotificationsInhibited);
     if (!nextEvent.isValid())
         return;   // there are no alarms pending
     const KADateTime now = KADateTime::currentDateTime(Preferences::timeSpec());
@@ -981,13 +1003,15 @@ void KAlarmApp::processQueue()
             const QueuedAction action = static_cast<QueuedAction>(int(entry.action) & int(QueuedAction::ActionMask));
 
             bool ok = true;
+            bool inhibit = false;
             if (entry.eventId.isEmpty())
             {
                 // It's a new alarm
                 switch (action)
                 {
                     case QueuedAction::Trigger:
-                        execAlarm(entry.event, entry.event.firstAlarm());
+                        if (execAlarm(entry.event, entry.event.firstAlarm()) == (void*)-2)
+                            inhibit = true;
                         break;
                     case QueuedAction::Handle:
                     {
@@ -1034,13 +1058,22 @@ void KAlarmApp::processQueue()
                 }
                 else
                 {
-                    ok = handleEvent(entry.eventId, action, findUniqueId);
-                    if (!ok  &&  exitAfter)
+                    // Trigger the event if it's due.
+                    const int result = handleEvent(entry.eventId, action, findUniqueId);
+                    if (!result)
+                        inhibit = true;
+                    else if (result < 0  &&  exitAfter)
                         CommandOptions::printError(xi18nc("@info:shell", "%1: Event <resource>%2</resource> not found, or not unique", mCommandOption, entry.eventId.eventId()));
                 }
             }
 
-            if (exitAfter)
+            if (inhibit)
+            {
+                // It's a display event which can't be executed because notifications
+                // are inhibited. Move it to the inhibited queue until the inhibition
+                // is removed.
+            }
+            else if (exitAfter)
             {
                 mActionQueue.clear();   // ensure that quitIf() actually exits the program
                 quitIf(ok ? 0 : 1);
@@ -1506,6 +1539,29 @@ void KAlarmApp::purge(int daysToKeep)
     processQueue();
 }
 
+/******************************************************************************
+* Called when the Freedesktop notifications properties have changed.
+* Check whether the inhibited property has changed.
+*/
+void KAlarmApp::slotFDOPropertiesChanged(const QString& interface,
+                                         const QVariantMap& changedProperties,
+                                         const QStringList& invalidatedProperties)
+{
+    Q_UNUSED(interface);     // always "org.freedesktop.Notifications"
+    Q_UNUSED(invalidatedProperties);
+    const auto it = changedProperties.find(QStringLiteral("Inhibited"));
+    if (it != changedProperties.end())
+    {
+        const bool inhibited = it.value().toBool();
+        if (inhibited != mNotificationsInhibited)
+        {
+            qCDebug(KALARM_LOG) << "KAlarmApp::slotFDOPropertiesChanged: Notifications inhibited ->" << inhibited;
+            mNotificationsInhibited = inhibited;
+            if (!mNotificationsInhibited)
+                QTimer::singleShot(0, this, &KAlarmApp::processQueue);
+        }
+    }
+}
 
 /******************************************************************************
 * Output a list of pending alarms, with their next scheduled occurrence.
@@ -1626,13 +1682,12 @@ bool KAlarmApp::scheduleEvent(KAEvent::SubAction action, const QString& text, co
     event.endChanges();
     if (alarmTime <= now)
     {
-        // Alarm is due for display already.
+        // Alarm is due for execution already.
         // First execute it once without adding it to the calendar file.
         qCDebug(KALARM_LOG) << "KAlarmApp::scheduleEvent: executing" << text;
-        if (!mInitialised)
+        if (!mInitialised
+        ||  execAlarm(event, event.firstAlarm()) == (void*)-2)
             mActionQueue.enqueue(ActionQEntry(event, QueuedAction::Trigger));
-        else
-            execAlarm(event, event.firstAlarm());
         // If it's a recurring alarm, reschedule it for its next occurrence
         if (!event.recurs()
         ||  event.setNextOccurrence(now) == KAEvent::NO_OCCURRENCE)
@@ -1673,17 +1728,20 @@ QString KAlarmApp::dbusList()
 
 /******************************************************************************
 * Either:
-* a) Display the event and then delete it if it has no outstanding repetitions.
+* a) Execute the event if it's due, and then delete it if it has no outstanding
+*    repetitions.
 * b) Delete the event.
 * c) Reschedule the event for its next repetition. If none remain, delete it.
 * If the event is deleted, it is removed from the calendar file and from every
 * main window instance.
 * If 'findUniqueId' is true and 'id' does not specify a resource, all resources
 * will be searched for the event's unique ID.
-* Reply = false if event ID not found, or if more than one event with the same
-*         ID is found.
+* Reply = -1 if event ID not found, or if more than one event with the same ID
+*            is found.
+*       =  0 if can't trigger display event because notifications are inhibited.
+*       =  1 if success.
 */
-bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUniqueId)
+int KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUniqueId)
 {
     Q_ASSERT(!(int(action) & ~int(QueuedAction::ActionMask)));
 
@@ -1700,7 +1758,7 @@ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUni
             qCWarning(KALARM_LOG) << "KAlarmApp::handleEvent: Event ID not found, or duplicated:" << eventID;
         else
             qCCritical(KALARM_LOG) << "KAlarmApp::handleEvent: No resource ID specified for event:" << eventID;
-        return false;
+        return -1;
     }
     switch (action)
     {
@@ -1850,7 +1908,7 @@ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUni
                         // All recurrences are finished, so cancel the event
                         event.setArchive();
                         if (cancelAlarm(event, alarm.type(), false))
-                            return true;   // event has been deleted
+                            return 1;   // event has been deleted
                         updateCalAndDisplay = true;
                         continue;
                     }
@@ -1868,7 +1926,7 @@ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUni
                             restart = true;
                             break;
                         case -1:
-                            return true;   // event has been deleted
+                            return 1;   // event has been deleted
                         default:
                             break;
                     }
@@ -1888,7 +1946,10 @@ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUni
             // If there is an alarm to execute, do this last after rescheduling/cancelling
             // any others. This ensures that the updated event is only saved once to the calendar.
             if (alarmToExecute.isValid())
-                execAlarm(event, alarmToExecute, Reschedule | (alarmToExecute.repeatAtLogin() ? NoExecFlag : AllowDefer));
+            {
+                if (execAlarm(event, alarmToExecute, Reschedule | (alarmToExecute.repeatAtLogin() ? NoExecFlag : AllowDefer)) == (void*)-2)
+                    return 0;    // display alarm, but notifications are inhibited
+            }
             else
             {
                 if (action == QueuedAction::Trigger)
@@ -1898,7 +1959,10 @@ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUni
                     // identical messages, for example.
                     const KAAlarm alarm = event.firstAlarm();
                     if (alarm.isValid())
-                        execAlarm(event, alarm);
+                    {
+                        if (execAlarm(event, alarm) == (void*)-2)
+                            return 0;    // display alarm, but notifications are inhibited
+                    }
                 }
                 if (updateCalAndDisplay)
                     KAlarm::updateEvent(event);     // update the window lists and calendar file
@@ -1909,7 +1973,7 @@ bool KAlarmApp::handleEvent(const EventId& id, QueuedAction action, bool findUni
         default:
             break;
     }
-    return true;
+    return 1;
 }
 
 /******************************************************************************
@@ -2098,9 +2162,10 @@ bool KAlarmApp::cancelReminderAndDeferral(KAEvent& event)
 * Execute an alarm by displaying its message or file, or executing its command.
 * Reply = ShellProcess instance if a command alarm
 *       = MessageWindow if an audio alarm
-*       != 0 if successful
+*       != null if successful
 *       = -1 if execution has not completed
-*       = 0 if the alarm is disabled, or if an error message was output.
+*       = -2 if can't execute display event because notifications are inhibited.
+*       = null if the alarm is disabled, or if an error message was output.
 */
 void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, ExecAlarmFlags flags)
 {
@@ -2113,6 +2178,14 @@ void* KAlarmApp::execAlarm(KAEvent& event, const KAAlarm& alarm, ExecAlarmFlags
         return nullptr;
     }
 
+    if (mNotificationsInhibited  &&  !(flags & NoNotifyInhibit)
+    &&  (event.actionTypes() & KAEvent::ACT_DISPLAY))
+    {
+        // It's a display event and notifications are inhibited.
+        qCDebug(KALARM_LOG) << "KAlarmApp::execAlarm:" << event.id() << ": notifications inhibited";
+        return (void*)-2;
+    }
+
     void* result = (void*)1;
     event.setArchive();
 
diff --git a/src/kalarmapp.h b/src/kalarmapp.h
index c7df5f46..76b32efa 100644
--- a/src/kalarmapp.h
+++ b/src/kalarmapp.h
@@ -43,7 +43,8 @@ public:
         Reschedule       = 0x01,  // reschedule the alarm after executing it
         AllowDefer       = 0x02,  // allow the alarm to be deferred
         NoRecordCmdError = 0x04,  // don't record command errors
-        NoPreAction      = 0x08
+        NoPreAction      = 0x08,  // it isn't a pre-alarm action
+        NoNotifyInhibit  = 0x10   // ignore notification inhibit
     };
     Q_DECLARE_FLAGS(ExecAlarmFlags, ExecAlarmFlag)
 
@@ -144,6 +145,9 @@ private Q_SLOTS:
     void               slotResourcePopulated(const Resource&);
     void               slotPurge()                     { purge(mArchivedPurgeDays); }
     void               slotCommandExited(ShellProcess*);
+    void               slotFDOPropertiesChanged(const QString& interface,
+                                                const QVariantMap& changedProperties,
+                                                const QStringList& invalidatedProperties);
 
 private:
     // Actions to execute in processQueue(). May be OR'ed together.
@@ -217,7 +221,7 @@ private:
     void               checkArchivedCalendar();
     void               queueAlarmId(const KAEvent&);
     bool               dbusHandleEvent(const EventId&, QueuedAction);
-    bool               handleEvent(const EventId&, QueuedAction, bool findUniqueId = false);
+    int                handleEvent(const EventId&, QueuedAction, bool findUniqueId = false);
     int                rescheduleAlarm(KAEvent&, const KAAlarm&, bool updateCalAndDisplay,
                                        const KADateTime& nextDt = KADateTime());
     bool               cancelAlarm(KAEvent&, KAAlarm::Type, bool updateCalAndDisplay);
@@ -266,6 +270,7 @@ private:
     bool               mKOrganizerEnabled;      // KOrganizer options are enabled (korganizer exists)
     bool               mWindowFocusBroken;      // keyboard focus transfer between windows doesn't work
     bool               mResourcesTimedOut {false}; // timeout has expired for populating resources
+    bool               mNotificationsInhibited {false};  // Freedesktop notifications are inhibited
 };
 
 inline KAlarmApp* theApp()  { return KAlarmApp::instance(); }
diff --git a/src/resourcescalendar.cpp b/src/resourcescalendar.cpp
index 3c6d5714..a0509818 100644
--- a/src/resourcescalendar.cpp
+++ b/src/resourcescalendar.cpp
@@ -20,6 +20,7 @@ using namespace KAlarmCal;
 ResourcesCalendar*             ResourcesCalendar::mInstance {nullptr};
 ResourcesCalendar::ResourceMap ResourcesCalendar::mResourceMap;
 ResourcesCalendar::EarliestMap ResourcesCalendar::mEarliestAlarm;
+ResourcesCalendar::EarliestMap ResourcesCalendar::mEarliestNonDispAlarm;
 QSet<QString>                  ResourcesCalendar::mPendingAlarms;
 bool                           ResourcesCalendar::mIgnoreAtLogin {false};
 bool                           ResourcesCalendar::mHaveDisabledAlarms {false};
@@ -111,6 +112,7 @@ void ResourcesCalendar::removeKAEvents(ResourceId key, bool closing, CalEvent::T
     if (removed)
     {
         mEarliestAlarm.remove(key);
+        mEarliestNonDispAlarm.remove(key);
         // Emit signal only if we're not in the process of closing the calendar
         if (!closing)
         {
@@ -193,17 +195,38 @@ void ResourcesCalendar::slotEventUpdated(Resource& resource, const KAEvent& even
     &&  event.category() == CalEvent::ACTIVE)
     {
         // Update the earliest alarm to trigger
-        const QString earliestId = mEarliestAlarm.value(key);
-        if (earliestId == event.id())
+        const QString earliestId        = mEarliestAlarm.value(key);
+        const QString earliestNonDispId = mEarliestNonDispAlarm.value(key);
+        if (earliestId == event.id()  ||  earliestNonDispId == event.id())
             findEarliestAlarm(resource);
         else
         {
             const KADateTime dt = event.nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
-            if (dt.isValid()
-            &&  (earliestId.isEmpty()  ||  dt < resource.event(earliestId).nextTrigger(KAEvent::ALL_TRIGGER)))
+            if (dt.isValid())
             {
-                mEarliestAlarm[key] = event.id();
-                Q_EMIT earliestAlarmChanged();
+                bool changed = false;
+                DateTime next;
+                if (!earliestId.isEmpty())
+                    next = resource.event(earliestId).nextTrigger(KAEvent::ALL_TRIGGER);
+                if (earliestId.isEmpty()  ||  dt < next)
+                {
+                    mEarliestAlarm[key] = event.id();
+                    changed = true;
+                }
+                if (!(event.actionTypes() & KAEvent::ACT_DISPLAY))
+                {
+                    // It is not a display event.
+                    DateTime nextNonDisp;
+                    if (!earliestNonDispId.isEmpty())
+                        nextNonDisp = (earliestId == earliestNonDispId) ? next : resource.event(earliestNonDispId).nextTrigger(KAEvent::ALL_TRIGGER);
+                    if (earliestNonDispId.isEmpty()  ||  dt < nextNonDisp)
+                    {
+                        mEarliestNonDispAlarm[key] = event.id();
+                        changed = true;
+                    }
+                }
+                if (changed)
+                    Q_EMIT earliestAlarmChanged();
             }
         }
     }
@@ -435,7 +458,8 @@ CalEvent::Type ResourcesCalendar::deleteEventInternal(const QString& eventID, co
 {
     const ResourceId key = resource.id();
     mResourceMap[key].remove(eventID);
-    if (mEarliestAlarm.value(key) == eventID)
+    if (mEarliestAlarm.value(key)        == eventID
+    ||  mEarliestNonDispAlarm.value(key) == eventID)
         mInstance->findEarliestAlarm(resource);
 
     CalEvent::Type status = CalEvent::EMPTY;
@@ -609,7 +633,8 @@ void ResourcesCalendar::checkForDisabledAlarms()
 }
 
 /******************************************************************************
-* Find and note the active alarm with the earliest trigger time for a calendar.
+* Find and note the active alarm with the earliest trigger time for a calendar,
+* and the non-display active alarm with the earliest trigger time.
 */
 void ResourcesCalendar::findEarliestAlarm(const Resource& resource)
 {
@@ -618,28 +643,46 @@ void ResourcesCalendar::findEarliestAlarm(const Resource& resource)
         return;
     if (!(resource.alarmTypes() & CalEvent::ACTIVE))
         return;
+
+    // Invalidate any existing earliest alarms for the resource
     EarliestMap::Iterator eit = mEarliestAlarm.find(key);
     if (eit != mEarliestAlarm.end())
         eit.value() = QString();
+    eit = mEarliestNonDispAlarm.find(key);
+    if (eit != mEarliestNonDispAlarm.end())
+        eit.value() = QString();
+
     ResourceMap::ConstIterator rit = mResourceMap.constFind(key);
     if (rit == mResourceMap.constEnd())
         return;
     const QVector<KAEvent> events = eventsForResource(resource, rit.value());
-    KAEvent earliest;
-    KADateTime earliestTime;
+    KAEvent earliest, earliestNonDisp;
+    KADateTime earliestTime, earliestNonDispTime;
     for (const KAEvent& event : events)
     {
         if (event.category() != CalEvent::ACTIVE
         ||  mPendingAlarms.contains(event.id()))
             continue;
         const KADateTime dt = event.nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
-        if (dt.isValid()  &&  (!earliest.isValid() || dt < earliestTime))
+        if (dt.isValid())
         {
-            earliestTime = dt;
-            earliest = event;
+            if (!earliest.isValid() || dt < earliestTime)
+            {
+                earliestTime = dt;
+                earliest = event;
+            }
+            if (!(event.actionTypes() & KAEvent::ACT_DISPLAY))
+            {
+                if (!earliestNonDisp.isValid() || dt < earliestNonDispTime)
+                {
+                    earliestNonDispTime = dt;
+                    earliestNonDisp = event;
+                }
+            }
         }
     }
-    mEarliestAlarm[key] = earliest.id();
+    mEarliestAlarm[key]        = earliest.id();
+    mEarliestNonDispAlarm[key] = earliestNonDisp.id();
     Q_EMIT earliestAlarmChanged();
 }
 
@@ -647,11 +690,12 @@ void ResourcesCalendar::findEarliestAlarm(const Resource& resource)
 * Return the active alarm with the earliest trigger time.
 * Reply = invalid if none.
 */
-KAEvent ResourcesCalendar::earliestAlarm(KADateTime& nextTriggerTime)
+KAEvent ResourcesCalendar::earliestAlarm(KADateTime& nextTriggerTime, bool excludeDisplayAlarms)
 {
     KAEvent earliest;
     KADateTime earliestTime;
-    for (EarliestMap::ConstIterator eit = mEarliestAlarm.constBegin();  eit != mEarliestAlarm.constEnd();  ++eit)
+    const EarliestMap& earliestAlarms(excludeDisplayAlarms ? mEarliestNonDispAlarm : mEarliestAlarm);
+    for (EarliestMap::ConstIterator eit = earliestAlarms.constBegin();  eit != earliestAlarms.constEnd();  ++eit)
     {
         const QString id = eit.value();
         if (id.isEmpty())
@@ -663,8 +707,9 @@ KAEvent ResourcesCalendar::earliestAlarm(KADateTime& nextTriggerTime)
             // Something went wrong: mEarliestAlarm wasn't updated when it should have been!!
             qCCritical(KALARM_LOG) << "ResourcesCalendar::earliestAlarm: resource" << eit.key() << "does not contain" << id;
             mInstance->findEarliestAlarm(res);
-            return earliestAlarm(nextTriggerTime);
+            return earliestAlarm(nextTriggerTime, excludeDisplayAlarms);
         }
+//TODO: use next trigger calculated in findEarliestAlarm() (allowing for it being out of date)?
         const KADateTime dt = event.nextTrigger(KAEvent::ALL_TRIGGER).effectiveKDateTime();
         if (dt.isValid()  &&  (!earliest.isValid() || dt < earliestTime))
         {
diff --git a/src/resourcescalendar.h b/src/resourcescalendar.h
index 04c65282..f679e7f6 100644
--- a/src/resourcescalendar.h
+++ b/src/resourcescalendar.h
@@ -38,10 +38,11 @@ public:
     static void             terminate();
 
     /** Return the active alarm with the earliest trigger time.
-     *  @param nextTriggerTime  The next trigger time of the earliest alarm.
+     *  @param nextTriggerTime       The next trigger time of the earliest alarm.
+     *  @param excludeDisplayAlarms  Ignore display alarms.
      *  @return  The earliest alarm.
      */
-    static KAEvent          earliestAlarm(KADateTime& nextTriggerTime);
+    static KAEvent          earliestAlarm(KADateTime& nextTriggerTime, bool excludeDisplayAlarms = false);
 
     static void             setAlarmPending(const KAEvent&, bool pending = true);
     static bool             haveDisabledAlarms()       { return mHaveDisabledAlarms; }
@@ -90,7 +91,8 @@ private:
     typedef QHash<ResourceId, QString> EarliestMap;  // event ID of earliest alarm, for each resource
 
     static ResourceMap    mResourceMap;
-    static EarliestMap    mEarliestAlarm;      // alarm with earliest trigger time, by resource
+    static EarliestMap    mEarliestAlarm;        // alarm with earliest trigger time, by resource
+    static EarliestMap    mEarliestNonDispAlarm; // non-display alarm with earliest trigger time, by resource
     static QSet<QString>  mPendingAlarms;      // IDs of alarms which are currently being processed after triggering
     static bool           mIgnoreAtLogin;      // ignore new/updated repeat-at-login alarms
     static bool           mHaveDisabledAlarms; // there is at least one individually disabled alarm


More information about the kde-doc-english mailing list