File indexing completed on 2024-11-17 05:15:19
0001 /* 0002 * Copyright 2020 Han Young <hanyoung@protonmail.com> 0003 * Copyright 2020-2022 Devin Lin <devin@kde.org> 0004 * 0005 * SPDX-License-Identifier: GPL-2.0-or-later 0006 */ 0007 0008 #include "alarm.h" 0009 0010 #include "alarmadaptor.h" 0011 #include "kclockdsettings.h" 0012 #include "utilities.h" 0013 0014 #include <KConfigGroup> 0015 #include <KSharedConfig> 0016 0017 #include <QDBusConnection> 0018 #include <QDBusMessage> 0019 #include <QDateTime> 0020 #include <QJsonDocument> 0021 #include <QJsonObject> 0022 0023 // alarm from json (loaded from storage) 0024 Alarm::Alarm(const QString &serialized, AlarmModel *parent) 0025 : QObject{parent} 0026 { 0027 if (!serialized.isEmpty()) { 0028 QJsonDocument doc = QJsonDocument::fromJson(serialized.toUtf8()); 0029 QJsonObject obj = doc.object(); 0030 0031 m_uuid = QUuid::fromString(obj[QStringLiteral("uuid")].toString()); 0032 m_name = obj[QStringLiteral("name")].toString(); 0033 m_enabled = obj[QStringLiteral("enabled")].toBool(); 0034 m_hours = obj[QStringLiteral("hours")].toInt(); 0035 m_minutes = obj[QStringLiteral("minutes")].toInt(); 0036 m_daysOfWeek = obj[QStringLiteral("daysOfWeek")].toInt(); 0037 m_audioPath = QUrl::fromLocalFile(obj[QStringLiteral("audioPath")].toString()); 0038 m_ringDuration = obj[QStringLiteral("ringDuration")].toInt(5); 0039 m_snoozeDuration = obj[QStringLiteral("snoozeDuration")].toInt(5); 0040 m_snoozedLength = obj[QStringLiteral("snoozedLength")].toInt(); 0041 } 0042 0043 init(parent); 0044 } 0045 0046 Alarm::Alarm(const QString &name, int hours, int minutes, int daysOfWeek, QString audioPath, int ringDuration, int snoozeDuration, AlarmModel *parent) 0047 : QObject{parent} 0048 , m_uuid{QUuid::createUuid()} 0049 , m_name{name} 0050 , m_enabled{true} // default the alarm to be on 0051 , m_hours{hours} 0052 , m_minutes{minutes} 0053 , m_daysOfWeek{daysOfWeek} 0054 , m_audioPath{QUrl::fromLocalFile(audioPath.replace(QStringLiteral("file://"), QString()))} 0055 , m_ringDuration{ringDuration} 0056 , m_snoozeDuration{snoozeDuration} 0057 { 0058 init(parent); 0059 } 0060 0061 Alarm::~Alarm() 0062 { 0063 // ensure alarm doesn't continue ringing after deletion 0064 if (m_ringing) { 0065 AlarmPlayer::instance().stop(); 0066 } 0067 } 0068 0069 void Alarm::init(AlarmModel *parent) 0070 { 0071 // setup notification 0072 m_notification->setIconName(QStringLiteral("kclock")); 0073 m_notification->setTitle(name() == QString() ? i18n("Alarm") : name()); 0074 m_notification->setText(QLocale::system().toString(QTime::currentTime(), QLocale::ShortFormat)); 0075 m_notification->setAutoDelete(false); // don't auto-delete when closing 0076 0077 auto defaultAction = m_notification->addDefaultAction(i18n("View")); 0078 connect(defaultAction, &KNotificationAction::activated, this, &Alarm::dismiss); 0079 0080 auto dismissAction = m_notification->addAction(i18n("Dismiss")); 0081 connect(dismissAction, &KNotificationAction::activated, this, &Alarm::dismiss); 0082 0083 auto snoozeAction = m_notification->addAction(i18n("Snooze")); 0084 connect(snoozeAction, &KNotificationAction::activated, this, &Alarm::snooze); 0085 0086 connect(m_notification, &KNotification::closed, this, &Alarm::dismiss); 0087 0088 // setup signals 0089 connect(this, &Alarm::rescheduleRequested, this, &Alarm::save); 0090 connect(this, &Alarm::rescheduleRequested, this, &Alarm::calculateNextRingTime); 0091 calculateNextRingTime(); 0092 0093 if (parent) { 0094 // must be after calculateNextTime signal, to ensure the ring time is calculated before the alarm is scheduled 0095 connect(this, &Alarm::rescheduleRequested, parent, &AlarmModel::scheduleAlarm); 0096 } 0097 0098 // setup dbus 0099 new AlarmAdaptor(this); 0100 QDBusConnection::sessionBus().registerObject(QStringLiteral("/Alarms/") + m_uuid.toString(QUuid::Id128), this); 0101 connect(this, &QObject::destroyed, [this] { 0102 QDBusConnection::sessionBus().unregisterObject(QStringLiteral("/Alarms/") + m_uuid.toString(QUuid::Id128), QDBusConnection::UnregisterNode); 0103 }); 0104 0105 // setup ringing length timer 0106 connect(m_ringTimer, &QTimer::timeout, this, []() { 0107 AlarmPlayer::instance().stop(); 0108 }); 0109 } 0110 0111 // alarm to json 0112 QString Alarm::serialize() 0113 { 0114 QJsonObject obj; 0115 obj[QStringLiteral("uuid")] = m_uuid.toString(); 0116 obj[QStringLiteral("name")] = m_name; 0117 obj[QStringLiteral("enabled")] = m_enabled; 0118 obj[QStringLiteral("hours")] = m_hours; 0119 obj[QStringLiteral("minutes")] = m_minutes; 0120 obj[QStringLiteral("daysOfWeek")] = m_daysOfWeek; 0121 obj[QStringLiteral("audioPath")] = m_audioPath.toLocalFile(); 0122 obj[QStringLiteral("ringDuration")] = m_ringDuration; 0123 obj[QStringLiteral("snoozeDuration")] = m_snoozeDuration; 0124 obj[QStringLiteral("snoozedLength")] = m_snoozedLength; 0125 QByteArray data = QJsonDocument(obj).toJson(QJsonDocument::Compact); 0126 return QString::fromStdString(data.toStdString()); 0127 } 0128 0129 QString Alarm::uuid() const 0130 { 0131 return m_uuid.toString(); 0132 } 0133 0134 QString Alarm::name() const 0135 { 0136 return m_name; 0137 } 0138 0139 void Alarm::setName(const QString &name) 0140 { 0141 if (name != m_name) { 0142 m_name = name; 0143 Q_EMIT nameChanged(); 0144 } 0145 } 0146 0147 bool Alarm::enabled() const 0148 { 0149 return m_enabled; 0150 } 0151 0152 void Alarm::setEnabled(bool enabled) 0153 { 0154 if (m_enabled != enabled) { 0155 setSnoozedLength(0); // reset snooze value 0156 setNextRingTime(0); // reset next ring time 0157 m_enabled = enabled; 0158 Q_EMIT enabledChanged(); 0159 Q_EMIT rescheduleRequested(); // notify the AlarmModel to reschedule 0160 } 0161 } 0162 0163 int Alarm::hours() const 0164 { 0165 return m_hours; 0166 } 0167 0168 void Alarm::setHours(int hours) 0169 { 0170 if (hours != m_hours) { 0171 m_hours = hours; 0172 Q_EMIT hoursChanged(); 0173 Q_EMIT rescheduleRequested(); 0174 } 0175 } 0176 0177 int Alarm::minutes() const 0178 { 0179 return m_minutes; 0180 } 0181 0182 void Alarm::setMinutes(int minutes) 0183 { 0184 if (minutes != m_minutes) { 0185 m_minutes = minutes; 0186 Q_EMIT minutesChanged(); 0187 Q_EMIT rescheduleRequested(); 0188 } 0189 } 0190 0191 int Alarm::daysOfWeek() const 0192 { 0193 return m_daysOfWeek; 0194 } 0195 0196 void Alarm::setDaysOfWeek(int daysOfWeek) 0197 { 0198 if (daysOfWeek != m_daysOfWeek) { 0199 m_daysOfWeek = daysOfWeek; 0200 Q_EMIT daysOfWeekChanged(); 0201 Q_EMIT rescheduleRequested(); 0202 } 0203 } 0204 0205 QString Alarm::audioPath() const 0206 { 0207 return m_audioPath.toLocalFile(); 0208 } 0209 0210 void Alarm::setAudioPath(QString path) 0211 { 0212 if (m_audioPath.path() != path) { 0213 path = path.replace(QStringLiteral("file://"), QString()); 0214 m_audioPath = QUrl::fromLocalFile(path); 0215 Q_EMIT audioPathChanged(); 0216 } 0217 } 0218 0219 int Alarm::ringDuration() const 0220 { 0221 return m_ringDuration; 0222 } 0223 0224 void Alarm::setRingDuration(int ringDuration) 0225 { 0226 if (ringDuration != m_ringDuration) { 0227 m_ringDuration = ringDuration; 0228 Q_EMIT ringDurationChanged(); 0229 } 0230 } 0231 0232 int Alarm::snoozeDuration() const 0233 { 0234 return m_snoozeDuration; 0235 } 0236 0237 void Alarm::setSnoozeDuration(int snoozeDuration) 0238 { 0239 if (snoozeDuration != m_snoozeDuration) { 0240 m_snoozeDuration = snoozeDuration; 0241 Q_EMIT snoozeDurationChanged(); 0242 } 0243 } 0244 0245 int Alarm::snoozedLength() const 0246 { 0247 return m_snoozedLength; 0248 } 0249 0250 void Alarm::setSnoozedLength(int snoozedLength) 0251 { 0252 if (snoozedLength != m_snoozedLength) { 0253 m_snoozedLength = snoozedLength; 0254 Q_EMIT snoozedLengthChanged(); 0255 } 0256 } 0257 0258 bool Alarm::ringing() const 0259 { 0260 return m_ringing; 0261 } 0262 0263 void Alarm::setRinging(bool ringing) 0264 { 0265 if (ringing != m_ringing) { 0266 m_ringing = ringing; 0267 Q_EMIT ringingChanged(); 0268 } 0269 } 0270 0271 void Alarm::save() 0272 { 0273 // save this alarm to the config 0274 auto config = KSharedConfig::openConfig(); 0275 KConfigGroup group = config->group(ALARM_CFG_GROUP); 0276 group.writeEntry(m_uuid.toString(), this->serialize()); 0277 group.sync(); 0278 } 0279 0280 void Alarm::ring() 0281 { 0282 if (!m_enabled) { 0283 return; 0284 } 0285 0286 qDebug() << "Ringing alarm" << m_name << "and sending notification..."; 0287 0288 // save ring time 0289 if (m_snoozedLength == 0) { 0290 m_originalRingTime = m_nextRingTime; 0291 } 0292 0293 // reset snoozed length (if snooze happens, this will get set again) 0294 setSnoozedLength(0); 0295 0296 // send notification 0297 m_notification->setText(QLocale::system().toString(QTime::currentTime(), QLocale::ShortFormat)); 0298 m_notification->sendEvent(); 0299 0300 // wake up device 0301 Utilities::wakeupNow(); 0302 0303 // pause playing mpris media sources 0304 Utilities::pauseMprisSources(); 0305 0306 // play sound (it will loop) 0307 qDebug() << "Alarm sound: " << m_audioPath; 0308 AlarmPlayer::instance().setSource(m_audioPath); 0309 AlarmPlayer::instance().play(); 0310 setRinging(true); 0311 0312 // stop ringing after duration 0313 m_ringTimer->start(m_ringDuration * 60 * 1000); 0314 } 0315 0316 void Alarm::dismiss() 0317 { 0318 qDebug() << "Alarm" << m_name << "dismissed"; 0319 0320 // ignore if the snooze button was pressed and dismiss is still called 0321 if (!m_justSnoozed && daysOfWeek() == 0) { 0322 // disable alarm if set to run once 0323 setEnabled(false); 0324 } 0325 m_justSnoozed = false; 0326 0327 AlarmPlayer::instance().stop(); 0328 Utilities::resumeMprisSources(); 0329 0330 setRinging(false); 0331 m_ringTimer->stop(); 0332 m_notification->close(); 0333 Q_EMIT rescheduleRequested(); 0334 0335 Utilities::instance().decfActiveCount(); 0336 } 0337 0338 void Alarm::snooze() 0339 { 0340 qDebug() << "Alarm snoozed (" << m_snoozeDuration << " minutes)"; 0341 AlarmPlayer::instance().stop(); 0342 Utilities::resumeMprisSources(); 0343 0344 setRinging(false); 0345 m_ringTimer->stop(); 0346 m_notification->close(); 0347 m_justSnoozed = true; 0348 0349 // add snooze amount, and enable alarm 0350 setSnoozedLength((QDateTime::currentSecsSinceEpoch() + 60 * m_snoozeDuration) - m_originalRingTime); 0351 m_enabled = true; // can't use setEnabled, since it will reset snoozedLength 0352 Q_EMIT enabledChanged(); 0353 Q_EMIT rescheduleRequested(); 0354 0355 Utilities::instance().decfActiveCount(); 0356 } 0357 0358 void Alarm::calculateNextRingTime() 0359 { 0360 if (!this->m_enabled) { // if not enabled, means this would never ring 0361 setNextRingTime(0); 0362 return; 0363 } 0364 0365 // get the time that the alarm will ring on the day 0366 QTime alarmTime = QTime(m_hours, m_minutes, 0).addSecs(m_snoozedLength); 0367 QDateTime date = QDateTime::currentDateTime(); 0368 0369 if (this->m_daysOfWeek == 0) { // alarm does not repeat (no days of the week are specified) 0370 // either the alarm occurs today or tomorrow 0371 bool alarmOccursToday = alarmTime >= date.time(); 0372 setNextRingTime(QDateTime(date.date().addDays(alarmOccursToday ? 0 : 1), alarmTime).toSecsSinceEpoch()); 0373 0374 } else { // repeat alarm 0375 bool first = true; 0376 0377 // keeping looping forward a single day until the day of week is accepted 0378 while (((this->m_daysOfWeek & (1 << (date.date().dayOfWeek() - 1))) == 0) // check day 0379 || (first && (alarmTime < date.time()))) // check time if the current day is accepted (keep looping forward if alarmTime has passed) 0380 { 0381 date = date.addDays(1); // go forward a day 0382 first = false; 0383 } 0384 0385 setNextRingTime(QDateTime(date.date(), alarmTime).toSecsSinceEpoch()); 0386 } 0387 } 0388 0389 void Alarm::setNextRingTime(quint64 nextRingTime) 0390 { 0391 if (m_nextRingTime != nextRingTime) { 0392 m_nextRingTime = nextRingTime; 0393 Q_EMIT nextRingTimeChanged(); 0394 } 0395 } 0396 0397 quint64 Alarm::nextRingTime() 0398 { 0399 // day changed, re-calculate 0400 if (this->m_nextRingTime <= static_cast<unsigned long long>(QDateTime::currentSecsSinceEpoch())) { 0401 calculateNextRingTime(); 0402 } 0403 return m_nextRingTime; 0404 }