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