File indexing completed on 2024-11-17 05:15:20

0001 /*
0002  * Copyright 2020 Han Young <hanyoung@protonmail.com>
0003  * Copyright 2021 Boris Petrov <boris.v.petrov@protonmail.com>
0004  * Copyright 2022 Devin Lin <devin@kde.org>
0005  *
0006  * SPDX-License-Identifier: GPL-2.0-or-later
0007  */
0008 
0009 #include "timer.h"
0010 
0011 Timer::Timer(int length, const QString &label, const QString &commandTimeout, bool running, QObject *parent)
0012     : QObject{parent}
0013     , m_uuid{QUuid::createUuid()}
0014     , m_length{length}
0015     , m_label{label}
0016     , m_commandTimeout{commandTimeout}
0017 {
0018     init();
0019 
0020     // start timer if requested
0021     if (running) {
0022         this->toggleRunning();
0023     }
0024 }
0025 
0026 Timer::Timer(const QJsonObject &obj, QObject *parent)
0027     : QObject{parent}
0028     , m_uuid{QUuid(obj[QStringLiteral("uuid")].toString())}
0029     , m_length{obj[QStringLiteral("length")].toInt()}
0030     , m_label{obj[QStringLiteral("label")].toString()}
0031     , m_commandTimeout{obj[QStringLiteral("commandTimeout")].toString()}
0032     , m_looping{obj[QStringLiteral("looping")].toBool()}
0033 {
0034     init();
0035 }
0036 
0037 Timer::~Timer()
0038 {
0039     if (!m_running) {
0040         // stop wakeup if timer is being deleted
0041         setRunning(false);
0042     }
0043 }
0044 
0045 void Timer::init()
0046 {
0047     // connect signals
0048     connect(&Utilities::instance(), &Utilities::wakeup, this, &Timer::timeUp);
0049     connect(&Utilities::instance(), &Utilities::needsReschedule, this, &Timer::reschedule);
0050 
0051     // initialize notification
0052     m_notification->setIconName(QStringLiteral("kclock"));
0053     m_notification->setTitle(i18n("Timer complete"));
0054     m_notification->setText(i18n("Your timer %1 has finished!", label()));
0055     m_notification->setUrgency(KNotification::HighUrgency);
0056     m_notification->setAutoDelete(false); // don't auto-delete when closing
0057 
0058     auto defaultAction = m_notification->addDefaultAction(i18n("View"));
0059     connect(defaultAction, &KNotificationAction::activated, this, &Timer::dismiss);
0060 
0061     connect(m_notification, &KNotification::closed, this, &Timer::dismiss);
0062 
0063     // initialize DBus object
0064     QDBusConnection::sessionBus().registerObject(QStringLiteral("/Timers/") + m_uuid.toString(QUuid::Id128),
0065                                                  this,
0066                                                  QDBusConnection::ExportScriptableContents | QDBusConnection::ExportAllProperties);
0067     connect(this, &QObject::destroyed, [this] {
0068         QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Timers/") + m_uuid.toString(QUuid::Id128), QDBusConnection::UnregisterNode);
0069     });
0070 }
0071 
0072 QJsonObject Timer::serialize()
0073 {
0074     QJsonObject obj;
0075     obj[QStringLiteral("length")] = m_length;
0076     obj[QStringLiteral("label")] = m_label;
0077     obj[QStringLiteral("uuid")] = m_uuid.toString();
0078     obj[QStringLiteral("looping")] = m_looping;
0079     obj[QStringLiteral("commandTimeout")] = m_commandTimeout;
0080     return obj;
0081 }
0082 
0083 void Timer::toggleRunning()
0084 {
0085     setRunning(!m_running);
0086 }
0087 
0088 void Timer::toggleLooping()
0089 {
0090     m_looping = !m_looping;
0091     Q_EMIT loopingChanged();
0092 
0093     TimerModel::instance()->save();
0094 }
0095 
0096 void Timer::reset()
0097 {
0098     setRunning(false);
0099     m_hasElapsed = 0;
0100 
0101     // ensure UI keeps up to date with hasElapsed
0102     Q_EMIT runningChanged();
0103 }
0104 
0105 int Timer::elapsed() const
0106 {
0107     if (running()) {
0108         return QDateTime::currentSecsSinceEpoch() - m_startTime;
0109     } else {
0110         return m_hasElapsed;
0111     }
0112 }
0113 
0114 QString Timer::uuid() const
0115 {
0116     return m_uuid.toString();
0117 }
0118 
0119 int Timer::length() const
0120 {
0121     return m_length;
0122 }
0123 
0124 void Timer::setLength(int length)
0125 {
0126     if (length != m_length) {
0127         m_length = length;
0128         Q_EMIT lengthChanged();
0129 
0130         TimerModel::instance()->save();
0131     }
0132 }
0133 
0134 QString Timer::label() const
0135 {
0136     return m_label;
0137 }
0138 
0139 void Timer::setLabel(const QString &label)
0140 {
0141     if (label != m_label) {
0142         m_label = label;
0143         Q_EMIT labelChanged();
0144 
0145         TimerModel::instance()->save();
0146     }
0147 }
0148 
0149 QString Timer::commandTimeout() const
0150 {
0151     return m_commandTimeout;
0152 }
0153 
0154 void Timer::setCommandTimeout(const QString &commandTimeout)
0155 {
0156     if (m_commandTimeout != commandTimeout) {
0157         m_commandTimeout = commandTimeout;
0158         Q_EMIT commandTimeoutChanged();
0159 
0160         TimerModel::instance()->save();
0161     }
0162 }
0163 
0164 bool Timer::looping() const
0165 {
0166     return m_looping;
0167 }
0168 
0169 bool Timer::running() const
0170 {
0171     return m_running;
0172 }
0173 
0174 bool Timer::ringing() const
0175 {
0176     return m_ringing;
0177 }
0178 
0179 void Timer::timeUp(int cookie)
0180 {
0181     if (cookie == m_cookie) {
0182         ring();
0183 
0184         // clear wakeup if it's somehow still there
0185         if (m_cookie > 0) {
0186             Utilities::instance().clearWakeup(m_cookie);
0187             m_cookie = -1;
0188         }
0189 
0190         // run command since timer has ended
0191         qDebug() << "Running command:" << m_commandTimeout;
0192         if (!m_commandTimeout.isEmpty()) {
0193             const QStringList commandAndArguments = QProcess::splitCommand(m_commandTimeout);
0194             QProcess::execute(commandAndArguments.front(), commandAndArguments.mid(1));
0195         }
0196 
0197         // loop if it is set
0198         if (m_looping) {
0199             reset();
0200             setRunning(true);
0201         } else {
0202             setRunning(false);
0203             m_hasElapsed = m_length;
0204         }
0205     }
0206 }
0207 
0208 void Timer::setRunning(bool running)
0209 {
0210     if (m_running == running) {
0211         return;
0212     }
0213 
0214     if (m_running) {
0215         m_hasElapsed = QDateTime::currentSecsSinceEpoch() - m_startTime;
0216 
0217         Utilities::instance().decfActiveCount();
0218 
0219         // clear wakeup
0220         if (m_cookie > 0) {
0221             Utilities::instance().clearWakeup(m_cookie);
0222             m_cookie = -1;
0223         }
0224     } else {
0225         if (m_hasElapsed == m_length) {
0226             // reset elapsed if the timer was already finished
0227             m_hasElapsed = 0;
0228         }
0229 
0230         // if we scheduled a wakeup before, cancel it first
0231         if (m_cookie > 0) {
0232             Utilities::instance().clearWakeup(m_cookie);
0233         }
0234 
0235         Utilities::instance().incfActiveCount();
0236 
0237         m_startTime = QDateTime::currentSecsSinceEpoch() - m_hasElapsed;
0238         m_cookie = Utilities::instance().scheduleWakeup(m_startTime + m_length);
0239     }
0240 
0241     m_running = running;
0242     Q_EMIT runningChanged();
0243 
0244     TimerModel::instance()->save();
0245 }
0246 
0247 void Timer::ring()
0248 {
0249     // if there were other ring events running, close them
0250     m_notification->close();
0251     
0252     qDebug("Timer finished, sending notification...");
0253     m_notification->sendEvent();
0254 
0255     Utilities::pauseMprisSources();
0256 
0257     m_ringing = true;
0258     Q_EMIT ringingChanged();
0259 }
0260 
0261 void Timer::dismiss()
0262 {
0263     qDebug() << "Timer dismissed.";
0264     m_notification->close();
0265 
0266     Utilities::resumeMprisSources();
0267 
0268     m_ringing = false;
0269     Q_EMIT ringingChanged();
0270 }
0271 
0272 void Timer::reschedule()
0273 {
0274     if (m_running) {
0275         m_cookie = Utilities::instance().scheduleWakeup(m_startTime + m_length);
0276     }
0277 }