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> > response =
128  m_upowerInterface->call(QLatin1String("EnumerateDevices"));
129  if (response.isValid())
130  {
131  QList devices = response.value();
132  for (const auto& device : qAsConst(devices))
133  DeviceAdded(device);
134  }
135 
136  if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "Changed", this, SLOT(Changed())))
137  {
138  LOG(VB_GENERAL, LOG_ERR, "Failed to register for Changed");
139  }
140 
141  if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "DeviceChanged", "o",
142  this, SLOT(DeviceChanged(QDBusObjectPath))))
143  {
144  LOG(VB_GENERAL, LOG_ERR, "Failed to register for DeviceChanged");
145  }
146 
147  if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "DeviceAdded", "o",
148  this, SLOT(DeviceAdded(QDBusObjectPath))))
149  {
150  LOG(VB_GENERAL, LOG_ERR, "Failed to register for DeviceAdded");
151  }
152 
153  if (!m_bus.connect(UPOWER_SERVICE, UPOWER_PATH, UPOWER_SERVICE, "DeviceRemoved", "o",
154  this, SLOT(DeviceRemoved(QDBusObjectPath))))
155  {
156  LOG(VB_GENERAL, LOG_ERR, "Failed to register for DeviceRemoved");
157  }
158  }
159 
160  Changed();
161  MythPower::Init();
162 }
163 
164 bool MythPowerDBus::DoFeature(bool Delayed)
165 {
166  if (!(m_logindInterface &&
167  ((m_features & m_scheduledFeature) != 0U) &&
168  (m_scheduledFeature != 0U)))
169  return false;
170 
171  if (!Delayed)
172  ReleaseLock();
173  switch (m_scheduledFeature)
174  {
175  case FeatureSuspend: m_logindInterface->call("Suspend", false); break;
176  case FeatureShutdown: m_logindInterface->call("PowerOff", false); break;
177  case FeatureHibernate: m_logindInterface->call("Hibernate", false); break;
178  case FeatureRestart: m_logindInterface->call("Reboot", false); break;
179  case FeatureHybridSleep: m_logindInterface->call("HybridSleep", false); break;
180  case FeatureNone: return false;
181  }
182  return true;
183 }
184 
185 void MythPowerDBus::DBusSuspending(bool Stopping)
186 {
187  if (Stopping)
188  {
190  return;
191 
192  if (UpdateStatus())
193  return;
194 
196  return;
197  }
198  DidWakeUp();
199 }
200 
202 {
203  if (Stopping)
204  {
206  return;
207 
208  if (UpdateStatus())
209  return;
210 
212  return;
213  }
214  DidWakeUp(); // after hibernate?
215 }
216 
218 {
220  return false;
221 
223  QVariant property = m_logindInterface->property("PreparingForShutdown");
224  if (property.isValid() && property.toBool())
226 
227  if (!feature)
228  {
229  property = m_logindInterface->property("PreparingForSleep");
230  if (property.isValid() && property.toBool())
232  }
233 
234  if (!feature)
235  return false;
236 
238 
239  // TODO It would be nice to check the ScheduledShutdown property to confirm
240  // the time available before shutdown/suspend but Qt doesn't like the type
241  // definition and aborts. Requires custom handling that is beyond the wit of this man.
242  LOG(VB_GENERAL, LOG_INFO, LOC + QString("System will %1").arg(FeatureToString(feature)));
243 
244  // Attempt to delay the action.
245 
246  // NB we don't care about user preference here. We are giving
247  // MythTV interested components an opportunity to cleanup before
248  // an externally initiated shutdown/suspend
249  uint delay = qBound(static_cast<uint>(0), m_maxRequestedDelay, m_maxSupportedDelay);
250  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Trying to delay system %1 for %2 seconds")
251  .arg(FeatureToString(feature)).arg(delay));
252  delay *= 1000;
253  m_delayTimer.start(static_cast<int>(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 
287 {
288  if (!MythPower::ScheduleFeature(Type, Delay))
289  return false;
290 
291  if (Delay < 1)
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  struct timespec time {};
300  if (clock_gettime(CLOCK_REALTIME, &time) == 0)
301  {
302  auto nanosecs = static_cast<quint64>((time.tv_sec * 1000000000) +
303  time.tv_nsec + (Delay * 1000000000));
304  QLatin1String type;
305  switch (Type)
306  {
307  case FeatureShutdown: type = QLatin1String("poweroff"); break;
308  case FeatureRestart: type = QLatin1String("reboot"); break;
309  default: break;
310  }
311  QDBusReply<void> reply =
312  m_logindInterface->call(QLatin1String("ScheduleShutdown"), type, nanosecs / 1000);
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(static_cast<int>(Delay) * 1000);
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()).arg(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(static_cast<int>(Delay) * 1000);
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  m_maxSupportedDelay = delay.toUInt() / 1000000; // Micro seconds to milliseconds
427  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Max inhibit delay: %1seconds").arg(m_maxSupportedDelay));
428  }
429  }
430 }
431 
433 {
434  int newlevel = UnknownPower;
435 
436  if (m_onBattery)
437  {
438  QMutexLocker locker(&s_lock);
439 
440  qreal total = 0;
441  int count = 0;
442 
443  // take an average (who has more than 1 battery?)
444  for (int level : qAsConst(m_batteries))
445  {
446  if (level >= 0 && level <= 100)
447  {
448  count++;
449  total += static_cast<qreal>(level);
450  }
451  }
452 
453  if (count > 0)
454  newlevel = lround(total / count);
455  }
456 
458  {
459  QVariant acpower = m_logindInterface->property("OnExternalPower");
460  if (acpower.isValid() && acpower.toBool())
461  newlevel = ACPower;
462  }
463 
464  PowerLevelChanged(newlevel);
465 }
466 
467 int MythPowerDBus::RetrieveBatteryLevel(const QString &Path)
468 {
469  QDBusInterface interface(UPOWER_SERVICE, Path, UPOWER_SERVICE + ".Device", m_bus);
470 
471  if (interface.isValid())
472  {
473  QVariant battery = interface.property("IsRechargeable");
474  if (battery.isValid() && battery.toBool())
475  {
476  QVariant percent = interface.property("Percentage");
477  if (percent.isValid())
478  {
479  int result = static_cast<int>(lroundf(percent.toFloat() * 100.0F));
480  if (result >= 0 && result <= 100)
481  {
482  m_onBattery = true;
483  return result;
484  }
485  }
486  }
487  else
488  {
489  QVariant type = interface.property("Type");
490  if (type.isValid())
491  {
492  QString typestr = type.toString();
493  if (typestr == "Line Power")
494  return ACPower;
495  if (typestr == "Ups")
496  return UPS;
497  }
498  }
499  }
500  return UnknownPower;
501 }
502 
514 void MythPowerDBus::AcquireLock(Features Types)
515 {
516  QMutexLocker locker(&s_lock);
517 
518  if (m_lockHandle > -1)
519  {
520  LOG(VB_GENERAL, LOG_WARNING, LOC + "Already hold delay lock");
521  ReleaseLock();
522  }
523 
524  QStringList types;
525  if (Types.testFlag(FeatureSuspend)) types << "sleep";
526  if (Types.testFlag(FeatureShutdown)) types << "shutdown";
527  if (types.isEmpty())
528  {
529  LOG(VB_GENERAL, LOG_ERR, LOC + "Unknown delay requests");
530  return;
531  }
532 
533  QDBusReply<QDBusUnixFileDescriptor> reply =
534  m_logindInterface->call(QLatin1String("Inhibit"), types.join(":").toLocal8Bit().constData(),
535  QLatin1String("MythTV"), QLatin1String(""), QLatin1String("delay"));
536  if (!reply.isValid())
537  {
538  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to delay %1: %2")
539  .arg(types.join(",")).arg(reply.error().message()));
540  m_lockHandle = -1;
541  return;
542  }
543 
544  m_lockHandle = dup(reply.value().fileDescriptor());
545  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Acquired delay FD: %1").arg(m_lockHandle));
546 }
547 
554 {
555  QMutexLocker locker(&s_lock);
556  if (m_lockHandle < 0)
557  return;
558 
559  if (m_scheduledFeature)
561 
562  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Releasing delay FD: %1").arg(m_lockHandle));
564  m_lockHandle = -1;
565 }
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:432
MythPowerDBus::DeviceChanged
void DeviceChanged(const QDBusObjectPath &Device)
Update power device state.
Definition: mythpowerdbus.cpp:389
MythPowerDBus::UpdateStatus
bool UpdateStatus(void)
Definition: mythpowerdbus.cpp:217
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:265
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:201
MythPowerDBus::m_delayTimer
QTimer m_delayTimer
Definition: mythpowerdbus.h:53
types
static const struct wl_interface * types[]
Definition: idle_inhibit_unstable_v1.c:39
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:360
MythPower::WillSuspend
void WillSuspend(uint MilliSeconds=0)
MythPowerDBus::ReleaseLock
void ReleaseLock(void)
Release our inhibition lock.
Definition: mythpowerdbus.cpp:553
MythPowerDBus::DoFeature
bool DoFeature(bool Delayed=false) override
Definition: mythpowerdbus.cpp:164
close
#define close
Definition: compat.h:17
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:185
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:14
mythpowerdbus.h
feature
static const std::array< featureStruct, 7 > feature
Definition: audiooutputsettings.cpp:433
MythPowerDBus::AcquireLock
void AcquireLock(Features Types)
Acquire an inhibition lock for logind power events.
Definition: mythpowerdbus.cpp:514
MythPowerDBus::Changed
void Changed(void)
Definition: mythpowerdbus.cpp:353
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:372
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:141
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:400
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:467
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:286
MythPower::WillShutDown
void WillShutDown(uint MilliSeconds=0)
LOC
#define LOC
Definition: mythpowerdbus.cpp:8
MythPower::Init
virtual void Init(void)
Definition: mythpower.cpp:126