/* * SPDX-FileCopyrightText: 2026 James Graham * SPDX-License-Identifier: GPL-2.0-only OR GPL-3.0-only OR LicenseRef-KDE-Accepted-GPL * */ #include "dateandtime.h" #include #include #include #include #include #include #include "datetime_logging.h" #include "message.h" #include "timedated_interface.h" using namespace Qt::StringLiterals; K_PLUGIN_CLASS_WITH_JSON(DateAndTime, "kcm_clock.json") namespace { constexpr inline auto NtpUnavailableKey = "ntpUnavailable"_L1; } DateAndTime::DateAndTime(QObject *parent, const KPluginMetaData &data) : KQuickConfigModule(parent, data) { const auto uri = "org.kde.plasma.private.kcm_clock"; qmlRegisterUncreatableType(uri, 1, 0, "message", QString()); connect(&m_refreshTimer, &QTimer::timeout, this, [this]() { refresh(); }); setButtons(Help | Apply); } QList DateAndTime::messages() const { return m_messages.values(); } void DateAndTime::addMessage(const Message &message, bool isTemporary, const QString &id) { if (!isTemporary) { m_messages[id] = message; Q_EMIT messagesChanged(); return; } const auto tempId = QString::number(m_nextKey); ++m_nextKey; m_messages[tempId] = message; Q_EMIT messagesChanged(); QTimer::singleShot(5000, this, [this, tempId]() { m_messages.remove(tempId); Q_EMIT messagesChanged(); }); } QString DateAndTime::timeZone() const { return timeZoneToUse().id(); } QTimeZone DateAndTime::timeZoneToUse() const { return m_timeZone.isValid() ? m_timeZone : m_systemTimeZone; } void DateAndTime::setTimeZone(QString timeZone) { auto oldTimeZone = timeZoneToUse(); if (oldTimeZone.id() == timeZone) { return; } if (timeZone == m_systemTimeZone.id()) { m_timeZone = {}; } else { m_timeZone = QTimeZone(timeZone.toLatin1()); } checkNeedsSave(); Q_EMIT timeZoneChanged(); Q_EMIT dateTimeChanged(); } QString DateAndTime::timeZoneString() const { return dateTime().toTimeZone(timeZoneToUse()).toString("tttt (t) UTC ttt "_L1); } bool DateAndTime::ntpAvailable() const { return m_ntpAvailable; } bool DateAndTime::ntpEnabled() const { return m_ntpEnabledSet ? m_ntpEnabled : m_systemNtpEnabled; } void DateAndTime::setNtpEnabled(bool ntpEnabled) { if (m_ntpEnabledSet && m_ntpEnabled == ntpEnabled) { return; } m_ntpEnabledSet = true; m_ntpEnabled = ntpEnabled; if (m_ntpEnabled) { m_dateTime = {}; } checkNeedsSave(); Q_EMIT ntpChanged(); } QDateTime DateAndTime::dateTime() const { return m_dateTime.isValid() ? m_dateTime : m_systemDateTime; } void DateAndTime::setDateTime(const QDateTime &dateTime) { if (m_ntpEnabled || m_dateTime == dateTime) { return; } m_dateTime = dateTime; checkNeedsSave(); Q_EMIT dateTimeChanged(); } void DateAndTime::setDate(const QDate &date) { if (m_ntpEnabled || m_dateTime.date() == date) { return; } m_dateTime.setDate(date); checkNeedsSave(); Q_EMIT dateTimeChanged(); } void DateAndTime::setTime(const QTime &time) { if (m_ntpEnabled || m_dateTime.time() == time) { return; } if (!m_dateTime.isValid()) { m_dateTime = m_systemDateTime; } m_dateTime.setTime(time); checkNeedsSave(); Q_EMIT dateTimeChanged(); } QString DateAndTime::dateTimeString() const { const auto dateString = dateTime().toTimeZone(timeZoneToUse()).toString(QLocale::system().dateFormat(QLocale::LongFormat)); auto longFormatNoTimeZone = QLocale::system().timeFormat(QLocale::LongFormat).remove(u"tttt"_s).trimmed(); const auto timeString = dateTime().toTimeZone(timeZoneToUse()).toString(longFormatNoTimeZone); return u"%1 %2"_s.arg(dateString, timeString); } void DateAndTime::checkNeedsSave() { bool timeZoneChanged = m_systemTimeZone.isValid() && m_timeZone.isValid() && m_systemTimeZone != m_timeZone; bool ntpChanged = m_systemNtpEnabledSet && m_ntpEnabledSet && m_systemNtpEnabled != m_ntpEnabled; bool dateTimeChanged = m_systemDateTime.isValid() && m_dateTime.isValid() && m_systemDateTime != m_dateTime; setNeedsSave(timeZoneChanged || ntpChanged || dateTimeChanged); } void DateAndTime::save() { if (timedateSave()) { QDBusMessage msg = QDBusMessage::createSignal("/org/kde/kcmshell_clock"_L1, "org.kde.kcmshell_clock"_L1, "clockUpdated"_L1); QDBusConnection::sessionBus().send(msg); } load(); } void DateAndTime::load() { m_systemTimeZone = {}; m_timeZone = {}; m_systemNtpEnabled = false; m_systemNtpEnabledSet = false; m_ntpEnabled = false; m_ntpEnabledSet = false; m_systemDateTime = {}; m_dateTime = {}; setNeedsSave(false); refresh(); m_refreshTimer.start(1000); } void DateAndTime::refresh() { if (!m_systemNtpEnabledSet || !m_ntpAvailable) { OrgFreedesktopTimedate1Interface timeDatedIface("org.freedesktop.timedate1"_L1, "/org/freedesktop/timedate1"_L1, QDBusConnection::systemBus()); const auto oldNtpAvailable = std::exchange(m_ntpAvailable, timeDatedIface.canNTP()); if (m_ntpAvailable) { m_messages.remove(NtpUnavailableKey); } else { addMessage(Message::error(i18n("Unable to change NTP settings")), false, NtpUnavailableKey); } const auto oldSystemNtpEnabled = std::exchange(m_systemNtpEnabled, timeDatedIface.nTP()); m_systemNtpEnabledSet = true; if (oldNtpAvailable != m_ntpAvailable || oldSystemNtpEnabled != m_systemNtpEnabled) { Q_EMIT ntpChanged(); } } // Timezone if (!m_systemTimeZone.isValid()) { const auto oldSystemTimeZone = std::exchange(m_systemTimeZone, QTimeZone::systemTimeZone()); if (oldSystemTimeZone != m_systemTimeZone) { Q_EMIT timeZoneChanged(); } } // Reset to the current date and time const auto oldSystemDateTime = std::exchange(m_systemDateTime, QDateTime::currentDateTime()); if (oldSystemDateTime != m_systemDateTime) { Q_EMIT dateTimeChanged(); Q_EMIT timeZoneChanged(); } } bool DateAndTime::timedateSave() { OrgFreedesktopTimedate1Interface timedateIface("org.freedesktop.timedate1"_L1, "/org/freedesktop/timedate1"_L1, QDBusConnection::systemBus()); // final arg in each method is "user-interaction" i.e whether it's OK for polkit to ask for auth // we cannot send requests up front then block for all replies as we need NTP to be disabled before we can make a call to SetTime // timedated processes these in parallel and will return an error otherwise if (m_ntpEnabledSet) { auto reply = timedateIface.SetNTP(m_ntpEnabled, true); reply.waitForFinished(); if (reply.isError()) { if (reply.error().name() != QDBusError::errorString(QDBusError::AccessDenied)) { addMessage(Message::error(i18n("Unable to change NTP settings"))); } else { addMessage(Message::error(i18n("Permission to change NTP setting denied"))); } qCWarning(KCM_CLOCK) << "Failed to enable NTP" << reply.error().name() << reply.error().message(); return false; } } if (!m_ntpEnabled && m_dateTime.isValid()) { //*1000 for milliseconds -> microseconds auto reply = timedateIface.SetTime(m_dateTime.toMSecsSinceEpoch() * 1000, false, true); reply.waitForFinished(); if (reply.isError()) { if (reply.error().name() != QDBusError::errorString(QDBusError::AccessDenied)) { addMessage(Message::error(i18n("Unable to set current time"))); } else { addMessage(Message::error(i18n("Permission to change date and time setting denied"))); } qCWarning(KCM_CLOCK) << "Failed to set current time" << reply.error().name() << reply.error().message(); return false; } } if (m_timeZone.isValid()) { auto reply = timedateIface.SetTimezone(m_timeZone.id(), true); reply.waitForFinished(); if (reply.isError()) { if (reply.error().name() != QDBusError::errorString(QDBusError::AccessDenied)) { addMessage(Message::error(i18n("Unable to set time zone"))); } else { addMessage(Message::error(i18n("Permission to change time zone denied"))); } qCWarning(KCM_CLOCK) << "Failed to set time zone" << reply.error().name() << reply.error().message(); return false; } } return true; } #include "dateandtime.moc"