Commit bf144124 authored by Bhushan Shah's avatar Bhushan Shah 📱
Browse files

[core] introduce the method to wake system up

When using aggressive power management on devices like mobile or laptop
devices, There's generally one objective, saving as much as power as
possible. To do this on some devices we keep system in deep
sleep/suspend for most of the time.

This means userspace will be frozen as well. For most part this is fine,
but for services like push notification helper, or update manager or for
example alarm application, this is not acceptable and it would mean that
we need to wake system at some point.

This helpers on org.kde.Solid.PowerManagement are supposed to help with
that.

You have two possibilities,

- Wake system instantly: this is useful when screen is turned off, but
  device is not in sleep, and you want to show e.g notifications on
  screen.
- Wake system in future: You want to schedule alarm or for example
  wakeup 15 mins later to check for notifications.

Once system is awake, powerdevil will ping dbus service of your choice
and call the method "wakeupCallback". By default this wakeup will be
silent as-in the DPMS and screen backlight will stay off, if you wish to
wake system fully, you can call "wakeup" method to turn system on fully.

Related: #2
parent 671da448
......@@ -120,7 +120,6 @@ qt5_add_dbus_adaptor(powerdevil_SRCS org.kde.Solid.PowerManagement.PolicyAgent.x
qt5_add_dbus_adaptor(powerdevil_SRCS org.freedesktop.PowerManagement.xml powerdevilfdoconnector.h PowerDevil::FdoConnector powermanagementfdoadaptor PowerManagementFdoAdaptor)
qt5_add_dbus_adaptor(powerdevil_SRCS org.freedesktop.PowerManagement.Inhibit.xml powerdevilfdoconnector.h PowerDevil::FdoConnector powermanagementinhibitadaptor PowerManagementInhibitAdaptor)
# Backends
add_subdirectory(backends)
......
<!DOCTYPE node PUBLIC "-//freedesktop//DTD D-BUS Object Introspection 1.0//EN" "http://www.freedesktop.org/standards/dbus/1.0/introspect.dtd">
<node>
<interface name="org.kde.PowerManagement">
<method name="wakeupCallback">
<arg type="i" direction="in" name="cookie"/>
</method>
</interface>
</node>
......@@ -28,6 +28,51 @@
<method name="hasDualGpu">
<arg type="b" direction="out" />
</method>
<!-- schedule system wakeup in future -->
<!--
This method allows to wake system instantly or in future.
There's 3 parameters
- dbus service
- dbus object path
- timestamp to wake system (in seconds from epoch)
This method may return 2 errors,
- org.kde.PowerDevil.Core.Wakeup.InvalidTime : If you try to schedule wakeup in past
- org.kde.PowerDevil.Core.Wakeup.Unsupported : If called on non-Linux platforms where
timer_fd interface is not supported
If wakeup is scheduled successfully, it returns the integer cookie which can be used by
the application for keeping track and referencing to it during wakeup
Upon waking up system, powerdevil will call the specified the dbus service and path on
interface org.kde.PowerManagement, wakeupCallback (XML for implementing this can be found
in this repo) method.
It will send out a single argument, which would be cookie number associated with the wakeup.
-->
<method name="scheduleWakeup">
<arg type="u" direction="out" />
<arg type="s" direction="in" name="service" />
<arg type="o" direction="in" name="path" />
<arg type="t" direction="in" name="timestamp"/>
</method>
<!-- wake system up immediately -->
<!-- this method can be used to wake system immediately from dpms off mode -->
<method name="wakeup" />
<!-- clear previously registered wakeups -->
<!--
This method clears out the previously registered wakeups and takes a single argument which
is the cookie supplied by the powerdevil earlier.
-->
<method name="clearWakeup">
<arg type="i" direction="in" />
</method>
<!--<method name="turnOffScreen" />-->
<!--<method name="getSupportedSuspendMethods">
......
......@@ -49,6 +49,12 @@
#include <QDebug>
#include <algorithm>
#ifdef Q_OS_LINUX
#include <sys/timerfd.h>
#endif
namespace PowerDevil
{
......@@ -196,6 +202,28 @@ void Core::onBackendReady()
QTimer::singleShot(30000, this, SLOT(onNotificationTimeout()));
}
#ifdef Q_OS_LINUX
// try creating a timerfd which can wake system from suspend
m_timerFd = timerfd_create(CLOCK_REALTIME_ALARM, TFD_CLOEXEC);
// if that fails due to privilges maybe, try normal timerfd
if (m_timerFd == -1) {
qCDebug(POWERDEVIL) << "Unable to create a CLOCK_REALTIME_ALARM timer, trying CLOCK_REALTIME\n This would mean that wakeup requests won't wake device from suspend";
m_timerFd = timerfd_create(CLOCK_REALTIME, TFD_CLOEXEC);
}
if (m_timerFd != -1) {
m_timerFdSocketNotifier = new QSocketNotifier(m_timerFd, QSocketNotifier::Read);
connect(m_timerFdSocketNotifier, &QSocketNotifier::activated, this, &Core::timerfdEventHandler);
// we disable events reading for now
m_timerFdSocketNotifier->setEnabled(false);
} else {
qCDebug(POWERDEVIL) << "Unable to create a CLOCK_REALTIME timer, scheduled wakeups won't be available";
}
#endif
// All systems up Houston, let's go!
Q_EMIT coreReady();
refreshStatus();
......@@ -925,6 +953,48 @@ bool Core::hasDualGpu() const
return m_hasDualGpu;
}
uint Core::scheduleWakeup(const QString &service, const QDBusObjectPath &path, qint64 timeout)
{
++m_lastWakeupCookie;
int cookie = m_lastWakeupCookie;
// if some one is trying to time travel, deny them
if (timeout < QDateTime::currentSecsSinceEpoch()) {
sendErrorReply(QDBusError::InvalidArgs, "You can not schedule wakeup in past");
} else {
#ifndef Q_OS_LINUX
sendErrorReply(QDBusError::NotSupported, "Scheduled wakeups are available only on Linux platforms");
#else
WakeupInfo wakeup{ service, path, cookie, timeout };
m_scheduledWakeups << wakeup;
qCDebug(POWERDEVIL) << "Received request to wakeup at " << QDateTime::fromSecsSinceEpoch(timeout);
resetAndScheduleNextWakeup();
#endif
}
return cookie;
}
void Core::wakeup()
{
onResumingFromIdle();
}
void Core::clearWakeup(int cookie)
{
// if we do not have any timeouts return from here
if (m_scheduledWakeups.isEmpty()) {
return;
}
// depending on cookie, remove it from scheduled wakeups
m_scheduledWakeups.erase(std::remove_if(m_scheduledWakeups.begin(), m_scheduledWakeups.end(), [cookie](WakeupInfo wakeup) {
return wakeup.cookie == cookie;
}));
// reset timerfd
resetAndScheduleNextWakeup();
}
qulonglong Core::batteryRemainingTime() const
{
return m_backend->batteryRemainingTime();
......@@ -935,4 +1005,63 @@ uint Core::backendCapabilities()
return m_backend->capabilities();
}
void Core::resetAndScheduleNextWakeup()
{
#ifdef Q_OS_LINUX
// first we sort the wakeup list
std::sort(m_scheduledWakeups.begin(), m_scheduledWakeups.end(), [](const WakeupInfo& lhs, const WakeupInfo& rhs)
{
return lhs.timeout < rhs.timeout;
});
// we don't want any of our wakeups to repeat'
timespec interval = {0, 0};
timespec nextWakeup;
bool enableNotifier = false;
// if we don't have any wakeups left, we call it a day and stop timer_fd
if(m_scheduledWakeups.isEmpty()) {
nextWakeup = {0, 0};
} else {
// now pick the first timeout from the list
WakeupInfo wakeup = m_scheduledWakeups.first();
nextWakeup = {wakeup.timeout, 0};
enableNotifier = true;
}
if (m_timerFd != -1) {
const itimerspec spec = {interval, nextWakeup};
timerfd_settime(m_timerFd, TFD_TIMER_ABSTIME, &spec, nullptr);
}
m_timerFdSocketNotifier->setEnabled(enableNotifier);
#endif
}
void Core::timerfdEventHandler()
{
// wakeup from the linux/rtc
// Disable reading events from the timer_fd
m_timerFdSocketNotifier->setEnabled(false);
// At this point scheduled wakeup list should not be empty, but just in case
if (m_scheduledWakeups.isEmpty()) {
qWarning(POWERDEVIL) << "Wakeup was recieved but list is now empty! This should not happen!";
return;
}
// first thing to do is, we pick the first wakeup from list
WakeupInfo currentWakeup = m_scheduledWakeups.takeFirst();
// Before doing anything further, lets set the next set of wakeup alarm
resetAndScheduleNextWakeup();
// Now current wakeup needs to be processed
// prepare message for sending back to the consumer
QDBusMessage msg = QDBusMessage::createMethodCall(currentWakeup.service, currentWakeup.path.path(),
QStringLiteral("org.kde.PowerManagement"), QStringLiteral("wakeupCallback"));
msg << currentWakeup.cookie;
// send it away
QDBusConnection::sessionBus().call(msg, QDBus::NoBlock);
}
}
......@@ -27,6 +27,13 @@
#include <QSet>
#include <QStringList>
#include <QDBusMessage>
#include <QDBusError>
#include <QDBusContext>
#include <QDBusObjectPath>
#include <QSocketNotifier>
#include <KSharedConfig>
namespace KActivities
......@@ -48,7 +55,14 @@ namespace PowerDevil
class BackendInterface;
class Action;
class Q_DECL_EXPORT Core : public QObject
struct WakeupInfo {
QString service;
QDBusObjectPath path;
int cookie;
qint64 timeout;
};
class Q_DECL_EXPORT Core : public QObject, protected QDBusContext
{
Q_OBJECT
Q_DISABLE_COPY(Core)
......@@ -87,6 +101,14 @@ public Q_SLOTS:
bool isActionSupported(const QString &actionName);
bool hasDualGpu() const;
// service - dbus interface to ping when wakeup is done
// path - dbus path on service
// cookie - data to pass back
// silent - true if silent wakeup is needed
uint scheduleWakeup(const QString &service, const QDBusObjectPath &path, qint64 timeout);
void wakeup();
void clearWakeup(int cookie);
Q_SIGNALS:
void coreReady();
void profileChanged(const QString &newProfile);
......@@ -134,6 +156,12 @@ private:
QSet<Action *> m_pendingResumeFromIdleActions;
bool m_pendingWakeupEvent;
// Scheduled wakeups and alarms
QList<WakeupInfo> m_scheduledWakeups;
int m_lastWakeupCookie = 0;
int m_timerFd = -1;
QSocketNotifier *m_timerFdSocketNotifier = nullptr;
// Activity inhibition management
QHash< QString, int > m_sessionActivityInhibit;
QHash< QString, int > m_screenActivityInhibit;
......@@ -153,6 +181,9 @@ private Q_SLOTS:
void onServiceRegistered(const QString &service);
void onLidClosedChanged(bool closed);
void onAboutToSuspend();
// handlers for handling wakeup dbus call
void resetAndScheduleNextWakeup();
void timerfdEventHandler();
};
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment