2
* Copyright (C) 2015 Canonical, Ltd.
4
* This program is free software: you can redistribute it and/or modify it under
5
* the terms of the GNU Lesser General Public License version 3, as published by
6
* the Free Software Foundation.
8
* This program is distributed in the hope that it will be useful, but WITHOUT
9
* ANY WARRANTY; without even the implied warranties of MERCHANTABILITY,
10
* SATISFACTORY QUALITY, or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
11
* Lesser General Public License for more details.
13
* You should have received a copy of the GNU Lesser General Public License
14
* along with this program. If not, see <http://www.gnu.org/licenses/>.
17
#include "sharedwakelock.h"
18
#include "abstractdbusservicemonitor.h"
21
#include <QDBusAbstractInterface>
22
#include <QDBusPendingCallWatcher>
23
#include <QDBusPendingReply>
28
const int POWERD_SYS_STATE_ACTIVE = 1; // copied from private header file powerd.h
29
const char cookieFile[] = "/tmp/qtmir_powerd_cookie";
32
* @brief The Wakelock class - wraps a single system wakelock
33
* Should the PowerD service vanish from the bus, the wakelock will be re-acquired when it re-joins the bus.
35
class Wakelock : public AbstractDBusServiceMonitor
39
Wakelock(const QDBusConnection &connection) noexcept
40
: AbstractDBusServiceMonitor(QStringLiteral("com.canonical.powerd"), QStringLiteral("/com/canonical/powerd"), QStringLiteral("com.canonical.powerd"), connection)
41
, m_wakelockEnabled(false)
43
// (re-)acquire wake lock when powerd (re-)appears on the bus
44
QObject::connect(this, &Wakelock::serviceAvailableChanged,
45
this, &Wakelock::onServiceAvailableChanged);
47
// WORKAROUND: if shell crashed while it held a wakelock, due to bug lp:1409722 powerd will not have released
48
// the wakelock for it. As workaround, we save the cookie to file and restore it if possible.
49
QFile cookieCache(cookieFile);
50
if (cookieCache.exists() && cookieCache.open(QFile::ReadOnly | QFile::Text)) {
51
m_wakelockEnabled = true;
52
m_cookie = cookieCache.readAll();
56
virtual ~Wakelock() noexcept
61
Q_SIGNAL void enabledChanged(bool);
64
return m_wakelockEnabled;
69
if (m_wakelockEnabled) { // wakelock already requested/set
72
m_wakelockEnabled = true;
79
QFile::remove(cookieFile);
81
if (!m_wakelockEnabled) { // no wakelock already requested/set
84
m_wakelockEnabled = false;
85
Q_EMIT enabledChanged(false);
87
if (!serviceAvailable()) {
88
qWarning() << "com.canonical.powerd DBus interface not available, presuming no wakelocks held";
92
if (!m_cookie.isEmpty()) {
93
dbusInterface()->asyncCall(QStringLiteral("clearSysState"), QString(m_cookie));
94
qCDebug(QTMIR_SESSIONS) << "Wakelock released" << m_cookie;
100
void onServiceAvailableChanged(bool available)
102
// Assumption is if service vanishes & reappears on the bus, it has lost its wakelock state and
103
// we must re-acquire if necessary
104
if (!m_wakelockEnabled) {
112
QFile::remove(cookieFile);
116
void onWakeLockAcquired(QDBusPendingCallWatcher *call)
118
QDBusPendingReply<QString> reply = *call;
119
if (reply.isError()) {
120
qCDebug(QTMIR_SESSIONS) << "Wakelock was NOT acquired, error:"
121
<< QDBusError::errorString(reply.error().type());
122
if (m_wakelockEnabled) {
123
m_wakelockEnabled = false;
124
Q_EMIT enabledChanged(false);
130
QByteArray cookie = reply.argumentAt<0>().toLatin1();
133
if (!m_wakelockEnabled || !m_cookie.isEmpty()) {
134
// notified wakelock was created, but we either don't want it, or already have one - release it immediately
135
dbusInterface()->asyncCall(QStringLiteral("clearSysState"), QString(cookie));
141
// see WORKAROUND above for why we save cookie to disk
142
QFile cookieCache(cookieFile);
143
cookieCache.open(QFile::WriteOnly | QFile::Text);
144
cookieCache.write(m_cookie);
146
qCDebug(QTMIR_SESSIONS) << "Wakelock acquired" << m_cookie;
147
Q_EMIT enabledChanged(true);
151
void acquireWakelock()
153
if (!serviceAvailable()) {
154
qWarning() << "com.canonical.powerd DBus interface not available, waiting for it";
158
QDBusPendingCall pcall = dbusInterface()->asyncCall(QStringLiteral("requestSysState"), "active", POWERD_SYS_STATE_ACTIVE);
160
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pcall, this);
161
QObject::connect(watcher, &QDBusPendingCallWatcher::finished,
162
this, &Wakelock::onWakeLockAcquired);
166
bool m_wakelockEnabled;
168
Q_DISABLE_COPY(Wakelock)
171
#include "sharedwakelock.moc"
174
* @brief SharedWakelock - allow a single wakelock instance to be shared between multiple owners
176
* QtMir has application management duties to perform even if display is off. To prevent device
177
* going to deep sleep before QtMir is ready, have QtMir register a system wakelock when it needs to.
179
* This class allows multiple objects to own the wakelock simultaneously. The wakelock is first
180
* registered when acquire has been called by one caller. Multiple callers may then share the
181
* wakelock. The wakelock is only destroyed when all callers have called release.
183
* Note a caller cannot have multiple shares of the wakelock. Multiple calls to acquire are ignored.
186
SharedWakelock::SharedWakelock(const QDBusConnection &connection)
187
: m_wakelock(new Wakelock(connection))
189
connect(m_wakelock.data(), &Wakelock::enabledChanged,
190
this, &SharedWakelock::enabledChanged);
193
// Define empty deconstructor here, as QScopedPointer<Wakelock> requires the destructor of the Wakelock class
194
// to be defined first.
195
SharedWakelock::~SharedWakelock()
199
bool SharedWakelock::enabled() const
201
return m_wakelock->enabled();
204
void SharedWakelock::acquire(const QObject *caller)
206
if (caller == nullptr || m_owners.contains(caller)) {
210
// register a slot to remove itself from owners list if destroyed
211
QObject::connect(caller, &QObject::destroyed, this, &SharedWakelock::release);
213
m_wakelock->acquire();
215
m_owners.insert(caller);
218
void SharedWakelock::release(const QObject *caller)
220
if (caller == nullptr || !m_owners.remove(caller)) {
224
QObject::disconnect(caller, &QObject::destroyed, this, 0);
226
if (m_owners.empty()) {
227
m_wakelock->release();