/* * Copyright (C) 2015 Canonical, Ltd. * * This program is free software: you can redistribute it and/or modify it under * the terms of the GNU Lesser General Public License version 3, as published by * the Free Software Foundation. * * This program is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranties of MERCHANTABILITY, * SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this program. If not, see . */ #include "sharedwakelock.h" #include "abstractdbusservicemonitor.h" #include "logging.h" #include #include #include #include namespace qtmir { const int POWERD_SYS_STATE_ACTIVE = 1; // copied from private header file powerd.h const char cookieFile[] = "/tmp/qtmir_powerd_cookie"; /** * @brief The Wakelock class - wraps a single system wakelock * Should the PowerD service vanish from the bus, the wakelock will be re-acquired when it re-joins the bus. */ class Wakelock : public AbstractDBusServiceMonitor { Q_OBJECT public: Wakelock(const QDBusConnection &connection) noexcept : AbstractDBusServiceMonitor("com.canonical.powerd", "/com/canonical/powerd", "com.canonical.powerd", connection) , m_wakelockEnabled(false) { // (re-)acquire wake lock when powerd (re-)appears on the bus QObject::connect(this, &Wakelock::serviceAvailableChanged, this, &Wakelock::onServiceAvailableChanged); // WORKAROUND: if shell crashed while it held a wakelock, due to bug lp:1409722 powerd will not have released // the wakelock for it. As workaround, we save the cookie to file and restore it if possible. QFile cookieCache(cookieFile); if (cookieCache.exists() && cookieCache.open(QFile::ReadOnly | QFile::Text)) { m_wakelockEnabled = true; m_cookie = cookieCache.readAll(); } } virtual ~Wakelock() noexcept { release(); } Q_SIGNAL void enabledChanged(bool); bool enabled() const { return m_wakelockEnabled; } void acquire() { if (m_wakelockEnabled) { // wakelock already requested/set return; } m_wakelockEnabled = true; acquireWakelock(); } void release() { QFile::remove(cookieFile); if (!m_wakelockEnabled) { // no wakelock already requested/set return; } m_wakelockEnabled = false; Q_EMIT enabledChanged(false); if (!serviceAvailable()) { qWarning() << "com.canonical.powerd DBus interface not available, presuming no wakelocks held"; return; } if (!m_cookie.isEmpty()) { dbusInterface()->asyncCall("clearSysState", QString(m_cookie)); qCDebug(QTMIR_SESSIONS) << "Wakelock released" << m_cookie; m_cookie.clear(); } } private Q_SLOTS: void onServiceAvailableChanged(bool available) { // Assumption is if service vanishes & reappears on the bus, it has lost its wakelock state and // we must re-acquire if necessary if (!m_wakelockEnabled) { return; } if (available) { acquireWakelock(); } else { m_cookie.clear(); QFile::remove(cookieFile); } } void onWakeLockAcquired(QDBusPendingCallWatcher *call) { QDBusPendingReply reply = *call; if (reply.isError()) { qCDebug(QTMIR_SESSIONS) << "Wakelock was NOT acquired, error:" << QDBusError::errorString(reply.error().type()); if (m_wakelockEnabled) { m_wakelockEnabled = false; Q_EMIT enabledChanged(false); } call->deleteLater(); return; } QByteArray cookie = reply.argumentAt<0>().toLatin1(); call->deleteLater(); if (!m_wakelockEnabled || !m_cookie.isEmpty()) { // notified wakelock was created, but we either don't want it, or already have one - release it immediately dbusInterface()->asyncCall("clearSysState", QString(cookie)); return; } m_cookie = cookie; // see WORKAROUND above for why we save cookie to disk QFile cookieCache(cookieFile); cookieCache.open(QFile::WriteOnly | QFile::Text); cookieCache.write(m_cookie); qCDebug(QTMIR_SESSIONS) << "Wakelock acquired" << m_cookie; Q_EMIT enabledChanged(true); } private: void acquireWakelock() { if (!serviceAvailable()) { qWarning() << "com.canonical.powerd DBus interface not available, waiting for it"; return; } QDBusPendingCall pcall = dbusInterface()->asyncCall("requestSysState", "active", POWERD_SYS_STATE_ACTIVE); QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this); QObject::connect(watcher, &QDBusPendingCallWatcher::finished, this, &Wakelock::onWakeLockAcquired); } QByteArray m_cookie; bool m_wakelockEnabled; Q_DISABLE_COPY(Wakelock) }; #include "sharedwakelock.moc" /** * @brief SharedWakelock - allow a single wakelock instance to be shared between multiple owners * * QtMir has application management duties to perform even if display is off. To prevent device * going to deep sleep before QtMir is ready, have QtMir register a system wakelock when it needs to. * * This class allows multiple objects to own the wakelock simultaneously. The wakelock is first * registered when acquire has been called by one caller. Multiple callers may then share the * wakelock. The wakelock is only destroyed when all callers have called release. * * Note a caller cannot have multiple shares of the wakelock. Multiple calls to acquire are ignored. */ SharedWakelock::SharedWakelock(const QDBusConnection &connection) : m_wakelock(new Wakelock(connection)) { connect(m_wakelock.data(), &Wakelock::enabledChanged, this, &SharedWakelock::enabledChanged); } // Define empty deconstructor here, as QScopedPointer requires the destructor of the Wakelock class // to be defined first. SharedWakelock::~SharedWakelock() { } bool SharedWakelock::enabled() const { return m_wakelock->enabled(); } void SharedWakelock::acquire(const QObject *caller) { if (caller == nullptr || m_owners.contains(caller)) { return; } // register a slot to remove itself from owners list if destroyed QObject::connect(caller, &QObject::destroyed, this, &SharedWakelock::release); m_wakelock->acquire(); m_owners.insert(caller); } void SharedWakelock::release(const QObject *caller) { if (caller == nullptr || !m_owners.remove(caller)) { return; } QObject::disconnect(caller, &QObject::destroyed, this, 0); if (m_owners.empty()) { m_wakelock->release(); } } } // namespace qtmir