MythTV master
mythpowerdbus.cpp
Go to the documentation of this file.
1// MythTV
2#include "mythlogging.h"
3#include "mythpowerdbus.h"
4
5// Std
6#include <unistd.h>
7#include <algorithm>
8
9// Qt
10#include <QDBusReply>
11#include <QDBusUnixFileDescriptor>
12
13#define LOC QString("PowerDBus: ")
14
15#define FREE_SERVICE (QString("org.freedesktop."))
16#define FREE_PATH (QString("/org/freedesktop/"))
17#define UPOWER (QString("UPower"))
18#define LOGIN1 (QString("login1"))
19#define UPOWER_SERVICE (FREE_SERVICE + UPOWER)
20#define UPOWER_PATH (FREE_PATH + UPOWER)
21#define UPOWER_INTERFACE (UPOWER_SERVICE)
22#define LOGIN1_SERVICE (FREE_SERVICE + LOGIN1)
23#define LOGIN1_PATH (FREE_PATH + LOGIN1)
24#define LOGIN1_INTERFACE (LOGIN1_SERVICE + QString(".Manager"))
25
47{
48 QMutexLocker locker(&s_lock);
49 static bool s_available = false;
50 static bool s_checked = false;
51 if (!s_checked)
52 {
53 s_checked = true;
54 auto* upower = new QDBusInterface(
55 UPOWER_SERVICE, UPOWER_PATH, UPOWER_INTERFACE, QDBusConnection::systemBus());
56 auto* login1 = new QDBusInterface(
57 LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_INTERFACE, QDBusConnection::systemBus());
58 s_available = upower->isValid() || login1->isValid();
59 delete upower;
60 delete login1;
61 }
62 return s_available;
63}
64
66{
67 m_delayTimer.setSingleShot(true);
70}
71
73{
77 LOG(VB_GENERAL, LOG_INFO, LOC + "Closing interfaces");
78 delete m_upowerInterface;
79 delete m_logindInterface;
80}
81
83{
84 // create interfaces
87
89 {
90 if (!m_upowerInterface->isValid())
91 {
92 delete m_upowerInterface;
93 m_upowerInterface = nullptr;
94 }
95 }
96
98 {
99 if (!m_logindInterface->isValid())
100 {
101 delete m_logindInterface;
102 m_logindInterface = nullptr;
103 }
104 }
105
107 LOG(VB_GENERAL, LOG_ERR, LOC + "No UPower interface. Unable to monitor battery state");
109 LOG(VB_GENERAL, LOG_WARNING, LOC + "No login1 interface. Cannot change system power state");
110
111 // listen for sleep/wake events
113 {
114 // delay system requests
116
117 if (!m_bus.connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_INTERFACE, "PrepareForSleep",
118 this, SLOT(DBusSuspending(bool))))
119 {
120 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to listen for sleep events");
121 }
122 if (!m_bus.connect(LOGIN1_SERVICE, LOGIN1_PATH, LOGIN1_INTERFACE, "PrepareForShutdown",
123 this, SLOT(DBusShuttingDown(bool))))
124 {
125 LOG(VB_GENERAL, LOG_WARNING, LOC + "Failed to listen for shutdown events");
126 }
127 }
128
129 // populate power devices (i.e. batteries)
131 {
132 QDBusReply<QList<QDBusObjectPath> > response =
133 m_upowerInterface->call(QLatin1String("EnumerateDevices"));
134 if (response.isValid())
135 {
136 QList devices = response.value();
137 for (const auto& device : std::as_const(devices))
138 DeviceAdded(device);
139 }
140
141 if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "Changed", this, SLOT(Changed())))
142 {
143 LOG(VB_GENERAL, LOG_ERR, "Failed to register for Changed");
144 }
145
146 if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "DeviceChanged", "o",
147 this, SLOT(DeviceChanged(QDBusObjectPath))))
148 {
149 LOG(VB_GENERAL, LOG_ERR, "Failed to register for DeviceChanged");
150 }
151
152 if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "DeviceAdded", "o",
153 this, SLOT(DeviceAdded(QDBusObjectPath))))
154 {
155 LOG(VB_GENERAL, LOG_ERR, "Failed to register for DeviceAdded");
156 }
157
158 if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "DeviceRemoved", "o",
159 this, SLOT(DeviceRemoved(QDBusObjectPath))))
160 {
161 LOG(VB_GENERAL, LOG_ERR, "Failed to register for DeviceRemoved");
162 }
163 }
164
165 Changed();
167}
168
169bool MythPowerDBus::DoFeature(bool Delayed)
170{
171 if (!m_logindInterface ||
172 ((m_features & m_scheduledFeature) == 0U) ||
173 (m_scheduledFeature == 0U))
174 return false;
175
176 if (!Delayed)
177 ReleaseLock();
178 switch (m_scheduledFeature)
179 {
180 case FeatureSuspend: m_logindInterface->call("Suspend", false); break;
181 case FeatureShutdown: m_logindInterface->call("PowerOff", false); break;
182 case FeatureHibernate: m_logindInterface->call("Hibernate", false); break;
183 case FeatureRestart: m_logindInterface->call("Reboot", false); break;
184 case FeatureHybridSleep: m_logindInterface->call("HybridSleep", false); break;
185 case FeatureNone: return false;
186 }
187 return true;
188}
189
191{
192 if (Stopping)
193 {
195 return;
196
197 if (UpdateStatus())
198 return;
199
201 return;
202 }
203 DidWakeUp();
204}
205
207{
208 if (Stopping)
209 {
211 return;
212
213 if (UpdateStatus())
214 return;
215
217 return;
218 }
219 DidWakeUp(); // after hibernate?
220}
221
223{
225 return false;
226
228 QVariant property = m_logindInterface->property("PreparingForShutdown");
229 if (property.isValid() && property.toBool())
231
232 if (!feature)
233 {
234 property = m_logindInterface->property("PreparingForSleep");
235 if (property.isValid() && property.toBool())
237 }
238
239 if (!feature)
240 return false;
241
243
244 // TODO It would be nice to check the ScheduledShutdown property to confirm
245 // the time available before shutdown/suspend but Qt doesn't like the type
246 // definition and aborts. Requires custom handling that is beyond the wit of this man.
247 LOG(VB_GENERAL, LOG_INFO, LOC + QString("System will %1").arg(FeatureToString(feature)));
248
249 // Attempt to delay the action.
250
251 // NB we don't care about user preference here. We are giving
252 // MythTV interested components an opportunity to cleanup before
253 // an externally initiated shutdown/suspend
255 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Trying to delay system %1 for %2 seconds")
256 .arg(FeatureToString(feature)).arg(delay.count()));
257 m_delayTimer.start(delay);
258
259 switch (feature)
260 {
261 case FeatureSuspend: emit WillSuspend(delay); break;
262 case FeatureShutdown: emit WillShutDown(delay); break;
263 default: break;
264 }
265
266 return true;
267}
268
270{
271 QMutexLocker locker(&s_lock);
272 m_delayTimer.stop();
275}
276
290bool MythPowerDBus::ScheduleFeature(enum Feature Type, std::chrono::seconds Delay)
291{
292 if (!MythPower::ScheduleFeature(Type, Delay))
293 return false;
294
295 if (Delay < 1s)
296 return true;
297
298 // try and use ScheduleShutdown as it gives the system the opportunity
299 // to inhibit shutdown and just plays nicely with other users - not least
300 // any mythbackend that is running. Suspend/hibernate are not supported.
301 if (m_logindInterface && (Type == FeatureShutdown || Type == FeatureRestart))
302 {
303 auto time = nowAsDuration<std::chrono::milliseconds>();
304 if (time > 0ms)
305 {
306 std::chrono::milliseconds millisecs = time + Delay;
307 QLatin1String type;
308 switch (Type)
309 {
310 case FeatureShutdown: type = QLatin1String("poweroff"); break;
311 case FeatureRestart: type = QLatin1String("reboot"); break;
312 default: break;
313 }
314 QDBusReply<void> reply =
315 m_logindInterface->call(QLatin1String("ScheduleShutdown"), type,
316 static_cast<qint64>(millisecs.count()));
317
318 if (reply.isValid() && !reply.error().isValid())
319 {
320 // cancel the default handling.
321 m_featureTimer.stop();
322 LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 scheduled via logind")
323 .arg(FeatureToString(Type)));
324 m_delayTimer.start(Delay);
325 }
326 else
327 {
328 LOG(VB_GENERAL, LOG_DEBUG, LOC +
329 QString("Failed to schedule %1 - falling back to default behaviour")
330 .arg(FeatureToString(Type)));
331 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Error %1 Message %2")
332 .arg(reply.error().name(), reply.error().message()));
333 }
334 }
335 }
336 else if (Type == FeatureSuspend)
337 {
338 // no logind scheduling but intiate suspend now and retain lock until ready
339 m_featureTimer.stop();
340 m_delayTimer.start(Delay);
341 DoFeature(true);
342 }
343
344 return true;
345}
346
349{
350 QMutexLocker locker(&s_lock);
351
352 if (m_delayTimer.isActive())
353 m_delayTimer.stop();
355}
356
358{
359 QMutexLocker locker(&s_lock);
362}
363
364void MythPowerDBus::DeviceAdded(const QDBusObjectPath& Device)
365{
366 {
367 QMutexLocker locker(&s_lock);
368 if (m_batteries.contains(Device.path()))
369 return;
370 m_batteries.insert(Device.path(), RetrieveBatteryLevel(Device.path()));
371 }
372 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added UPower.Device '%1'").arg(Device.path()));
374}
375
376void MythPowerDBus::DeviceRemoved(const QDBusObjectPath& Device)
377{
378 {
379 QMutexLocker locker(&s_lock);
380 if (!m_batteries.contains(Device.path()))
381 return;
382 m_batteries.remove(Device.path());
383 }
384 LOG(VB_GENERAL, LOG_INFO, QString("Removed UPower.Device '%1'").arg(Device.path()));
386}
387
393void MythPowerDBus::DeviceChanged(const QDBusObjectPath& Device)
394{
395 {
396 QMutexLocker locker(&s_lock);
397 if (!m_batteries.contains(Device.path()))
398 return;
400 }
402}
403
405{
407 m_onBattery = false;
408
410 {
411 QDBusReply<QString> cansuspend = m_logindInterface->call(QLatin1String("CanSuspend"));
412 if (cansuspend.isValid() && cansuspend.value() == "yes")
414 QDBusReply<QString> canshutdown = m_logindInterface->call(QLatin1String("CanPowerOff"));
415 if (canshutdown.isValid() && canshutdown.value() == "yes")
417 QDBusReply<QString> canrestart = m_logindInterface->call(QLatin1String("CanReboot"));
418 if (canrestart.isValid() && canrestart.value() == "yes")
420 QDBusReply<QString> canhibernate = m_logindInterface->call(QLatin1String("CanHibernate"));
421 if (canhibernate.isValid() && canhibernate.value() == "yes")
423 QDBusReply<QString> canhybrid = m_logindInterface->call(QLatin1String("CanHybridSleep"));
424 if (canhybrid.isValid() && canhybrid.value() == "yes")
426
427 QVariant delay = m_logindInterface->property("InhibitDelayMaxUSec");
428 if (delay.isValid())
429 {
430 auto value = std::chrono::microseconds(delay.toUInt());
431 m_maxSupportedDelay = duration_cast<std::chrono::seconds>(value);
432 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Max inhibit delay: %1seconds")
433 .arg(m_maxSupportedDelay.count()));
434 }
435 }
436}
437
439{
440 int newlevel = UnknownPower;
441
442 if (m_onBattery)
443 {
444 QMutexLocker locker(&s_lock);
445
446 qreal total = 0;
447 int count = 0;
448
449 // take an average (who has more than 1 battery?)
450 for (int level : std::as_const(m_batteries))
451 {
452 if (level >= 0 && level <= 100)
453 {
454 count++;
455 total += static_cast<qreal>(level);
456 }
457 }
458
459 if (count > 0)
460 newlevel = lround(total / count);
461 }
462
464 {
465 QVariant acpower = m_logindInterface->property("OnExternalPower");
466 if (acpower.isValid() && acpower.toBool())
467 newlevel = ACPower;
468 }
469
470 PowerLevelChanged(newlevel);
471}
472
474{
475 QDBusInterface interface(UPOWER_SERVICE, Path, UPOWER_SERVICE + ".Device", m_bus);
476
477 if (interface.isValid())
478 {
479 QVariant battery = interface.property("IsRechargeable");
480 if (battery.isValid() && battery.toBool())
481 {
482 QVariant percent = interface.property("Percentage");
483 if (percent.isValid())
484 {
485 int result = static_cast<int>(lroundf(percent.toFloat() * 100.0F));
486 if (result >= 0 && result <= 100)
487 {
488 m_onBattery = true;
489 return result;
490 }
491 }
492 }
493 else
494 {
495 QVariant type = interface.property("Type");
496 if (type.isValid())
497 {
498 QString typestr = type.toString();
499 if (typestr == "Line Power")
500 return ACPower;
501 if (typestr == "Ups")
502 return UPS;
503 }
504 }
505 }
506 return UnknownPower;
507}
508
520void MythPowerDBus::AcquireLock(Features Types)
521{
522 QMutexLocker locker(&s_lock);
523
524 if (m_lockHandle > -1)
525 {
526 LOG(VB_GENERAL, LOG_WARNING, LOC + "Already hold delay lock");
527 ReleaseLock();
528 }
529
530 QStringList types;
531 if (Types.testFlag(FeatureSuspend)) types << "sleep";
532 if (Types.testFlag(FeatureShutdown)) types << "shutdown";
533 if (types.isEmpty())
534 {
535 LOG(VB_GENERAL, LOG_ERR, LOC + "Unknown delay requests");
536 return;
537 }
538
539 QDBusReply<QDBusUnixFileDescriptor> reply =
540 m_logindInterface->call(QLatin1String("Inhibit"), types.join(":").toLocal8Bit().constData(),
541 QLatin1String("MythTV"), QLatin1String(""), QLatin1String("delay"));
542 if (!reply.isValid())
543 {
544 LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to delay %1: %2")
545 .arg(types.join(","), reply.error().message()));
546 m_lockHandle = -1;
547 return;
548 }
549
550 m_lockHandle = dup(reply.value().fileDescriptor());
551 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Acquired delay FD: %1").arg(m_lockHandle));
552}
553
560{
561 QMutexLocker locker(&s_lock);
562 if (m_lockHandle < 0)
563 return;
564
567
568 LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Releasing delay FD: %1").arg(m_lockHandle));
570 m_lockHandle = -1;
571}
static const std::array< featureStruct, 7 > feature
A device containing images (ie. USB stick, CD, storage group etc)
QDBusConnection m_bus
Definition: mythpowerdbus.h:51
void CancelFeature(void) override
This is untested.
void UpdateProperties(void)
QDBusInterface * m_upowerInterface
Definition: mythpowerdbus.h:52
void DidWakeUp(void) override
void DBusShuttingDown(bool Stopping)
void DeviceChanged(const QDBusObjectPath &Device)
Update power device state.
void AcquireLock(Features Types)
Acquire an inhibition lock for logind power events.
bool UpdateStatus(void)
void DBusSuspending(bool Stopping)
QTimer m_delayTimer
Definition: mythpowerdbus.h:55
void UpdateBattery(void)
bool ScheduleFeature(enum Feature Type, std::chrono::seconds Delay) override
Schedule a MythTV initiated power feature.
void DeviceAdded(const QDBusObjectPath &Device)
void ReleaseLock(void)
Release our inhibition lock.
static bool IsAvailable(void)
Static check for DBus interfaces that support some form of power management.
void DeviceRemoved(const QDBusObjectPath &Device)
int RetrieveBatteryLevel(const QString &Path)
void Changed(void)
bool DoFeature(bool Delayed=false) override
~MythPowerDBus() override
QMap< QString, int > m_batteries
Definition: mythpowerdbus.h:50
QDBusInterface * m_logindInterface
Definition: mythpowerdbus.h:53
void Init(void) override
@ UnknownPower
Definition: mythpower.h:31
std::chrono::seconds m_maxRequestedDelay
Definition: mythpower.h:94
static QRecursiveMutex s_lock
Definition: mythpower.h:76
void WillSuspend(std::chrono::milliseconds MilliSeconds=0ms)
@ FeatureRestart
Definition: mythpower.h:41
@ FeatureShutdown
Definition: mythpower.h:38
@ FeatureSuspend
Definition: mythpower.h:39
@ FeatureNone
Definition: mythpower.h:37
@ FeatureHibernate
Definition: mythpower.h:40
@ FeatureHybridSleep
Definition: mythpower.h:42
Features m_features
Definition: mythpower.h:91
virtual void FeatureHappening(Feature Spontaneous=FeatureNone)
Signal to the rest of MythTV that the given feature will happen now.
Definition: mythpower.cpp:301
virtual void DidWakeUp(void)
Definition: mythpower.cpp:328
Feature m_scheduledFeature
Definition: mythpower.h:92
QTimer m_featureTimer
Definition: mythpower.h:96
static bool FeatureIsEquivalent(Feature First, Feature Second)
Definition: mythpower.cpp:257
void PowerLevelChanged(int Level)
Definition: mythpower.cpp:345
virtual void Init(void)
Definition: mythpower.cpp:130
static QString FeatureToString(enum Feature Type)
Definition: mythpower.cpp:243
virtual void CancelFeature(void)
This is untested as it is currently not clear whether it is useful.
Definition: mythpower.cpp:222
std::chrono::seconds m_maxSupportedDelay
Definition: mythpower.h:95
void WillShutDown(std::chrono::milliseconds MilliSeconds=0ms)
virtual bool ScheduleFeature(enum Feature Type, std::chrono::seconds Delay)
Definition: mythpower.cpp:274
#define close
Definition: compat.h:30
static const struct wl_interface * types[]
#define LOG(_MASK_, _LEVEL_, _QSTRING_)
Definition: mythlogging.h:39
#define LOC
#define UPOWER_PATH
#define LOGIN1_PATH
#define LOGIN1_SERVICE
#define LOGIN1_INTERFACE
#define UPOWER_INTERFACE
#define UPOWER_SERVICE
static eu8 clamp(eu8 value, eu8 low, eu8 high)
Definition: pxsup2dast.c:206