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