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  auto delay = qBound(0s, 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.count()));
252  m_delayTimer.start(delay);
253 
254  switch (feature)
255  {
256  case FeatureSuspend: emit WillSuspend(delay); break;
257  case FeatureShutdown: emit WillShutDown(delay); break;
258  default: break;
259  }
260 
261  return true;
262 }
263 
265 {
266  QMutexLocker locker(&s_lock);
267  m_delayTimer.stop();
270 }
271 
285 bool MythPowerDBus::ScheduleFeature(enum Feature Type, std::chrono::seconds Delay)
286 {
287  if (!MythPower::ScheduleFeature(Type, Delay))
288  return false;
289 
290  if (Delay < 1s)
291  return true;
292 
293  // try and use ScheduleShutdown as it gives the system the opportunity
294  // to inhibit shutdown and just plays nicely with other users - not least
295  // any mythbackend that is running. Suspend/hibernate are not supported.
296  if (m_logindInterface && (Type == FeatureShutdown || Type == FeatureRestart))
297  {
298  auto time = nowAsDuration<std::chrono::milliseconds>();
299  if (time > 0ms)
300  {
301  std::chrono::milliseconds millisecs = time + Delay;
302  QLatin1String type;
303  switch (Type)
304  {
305  case FeatureShutdown: type = QLatin1String("poweroff"); break;
306  case FeatureRestart: type = QLatin1String("reboot"); break;
307  default: break;
308  }
309  QDBusReply<void> reply =
310  m_logindInterface->call(QLatin1String("ScheduleShutdown"), type,
311  static_cast<qint64>(millisecs.count()));
312 
313  if (reply.isValid() && !reply.error().isValid())
314  {
315  // cancel the default handling.
316  m_featureTimer.stop();
317  LOG(VB_GENERAL, LOG_INFO, LOC + QString("%1 scheduled via logind")
318  .arg(FeatureToString(Type)));
319  m_delayTimer.start(Delay);
320  }
321  else
322  {
323  LOG(VB_GENERAL, LOG_DEBUG, LOC +
324  QString("Failed to schedule %1 - falling back to default behaviour")
325  .arg(FeatureToString(Type)));
326  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Error %1 Message %2")
327  .arg(reply.error().name(), reply.error().message()));
328  }
329  }
330  }
331  else if (Type == FeatureSuspend)
332  {
333  // no logind scheduling but intiate suspend now and retain lock until ready
334  m_featureTimer.stop();
335  m_delayTimer.start(Delay);
336  DoFeature(true);
337  }
338 
339  return true;
340 }
341 
344 {
345  QMutexLocker locker(&s_lock);
346 
347  if (m_delayTimer.isActive())
348  m_delayTimer.stop();
350 }
351 
353 {
354  QMutexLocker locker(&s_lock);
356  UpdateBattery();
357 }
358 
359 void MythPowerDBus::DeviceAdded(const QDBusObjectPath& Device)
360 {
361  {
362  QMutexLocker locker(&s_lock);
363  if (m_batteries.contains(Device.path()))
364  return;
365  m_batteries.insert(Device.path(), RetrieveBatteryLevel(Device.path()));
366  }
367  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Added UPower.Device '%1'").arg(Device.path()));
368  UpdateBattery();
369 }
370 
371 void MythPowerDBus::DeviceRemoved(const QDBusObjectPath& Device)
372 {
373  {
374  QMutexLocker locker(&s_lock);
375  if (!m_batteries.contains(Device.path()))
376  return;
377  m_batteries.remove(Device.path());
378  }
379  LOG(VB_GENERAL, LOG_INFO, QString("Removed UPower.Device '%1'").arg(Device.path()));
380  UpdateBattery();
381 }
382 
388 void MythPowerDBus::DeviceChanged(const QDBusObjectPath& Device)
389 {
390  {
391  QMutexLocker locker(&s_lock);
392  if (!m_batteries.contains(Device.path()))
393  return;
394  m_batteries[Device.path()] = RetrieveBatteryLevel(Device.path());
395  }
396  UpdateBattery();
397 }
398 
400 {
402  m_onBattery = false;
403 
404  if (m_logindInterface)
405  {
406  QDBusReply<QString> cansuspend = m_logindInterface->call(QLatin1String("CanSuspend"));
407  if (cansuspend.isValid() && cansuspend.value() == "yes")
409  QDBusReply<QString> canshutdown = m_logindInterface->call(QLatin1String("CanPowerOff"));
410  if (canshutdown.isValid() && canshutdown.value() == "yes")
412  QDBusReply<QString> canrestart = m_logindInterface->call(QLatin1String("CanReboot"));
413  if (canrestart.isValid() && canrestart.value() == "yes")
415  QDBusReply<QString> canhibernate = m_logindInterface->call(QLatin1String("CanHibernate"));
416  if (canhibernate.isValid() && canhibernate.value() == "yes")
418  QDBusReply<QString> canhybrid = m_logindInterface->call(QLatin1String("CanHybridSleep"));
419  if (canhybrid.isValid() && canhybrid.value() == "yes")
421 
422  QVariant delay = m_logindInterface->property("InhibitDelayMaxUSec");
423  if (delay.isValid())
424  {
425  auto value = std::chrono::microseconds(delay.toUInt());
426  m_maxSupportedDelay = duration_cast<std::chrono::seconds>(value);
427  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Max inhibit delay: %1seconds")
428  .arg(m_maxSupportedDelay.count()));
429  }
430  }
431 }
432 
434 {
435  int newlevel = UnknownPower;
436 
437  if (m_onBattery)
438  {
439  QMutexLocker locker(&s_lock);
440 
441  qreal total = 0;
442  int count = 0;
443 
444  // take an average (who has more than 1 battery?)
445  for (int level : qAsConst(m_batteries))
446  {
447  if (level >= 0 && level <= 100)
448  {
449  count++;
450  total += static_cast<qreal>(level);
451  }
452  }
453 
454  if (count > 0)
455  newlevel = lround(total / count);
456  }
457 
459  {
460  QVariant acpower = m_logindInterface->property("OnExternalPower");
461  if (acpower.isValid() && acpower.toBool())
462  newlevel = ACPower;
463  }
464 
465  PowerLevelChanged(newlevel);
466 }
467 
468 int MythPowerDBus::RetrieveBatteryLevel(const QString &Path)
469 {
470  QDBusInterface interface(UPOWER_SERVICE, Path, UPOWER_SERVICE + ".Device", m_bus);
471 
472  if (interface.isValid())
473  {
474  QVariant battery = interface.property("IsRechargeable");
475  if (battery.isValid() && battery.toBool())
476  {
477  QVariant percent = interface.property("Percentage");
478  if (percent.isValid())
479  {
480  int result = static_cast<int>(lroundf(percent.toFloat() * 100.0F));
481  if (result >= 0 && result <= 100)
482  {
483  m_onBattery = true;
484  return result;
485  }
486  }
487  }
488  else
489  {
490  QVariant type = interface.property("Type");
491  if (type.isValid())
492  {
493  QString typestr = type.toString();
494  if (typestr == "Line Power")
495  return ACPower;
496  if (typestr == "Ups")
497  return UPS;
498  }
499  }
500  }
501  return UnknownPower;
502 }
503 
515 void MythPowerDBus::AcquireLock(Features Types)
516 {
517  QMutexLocker locker(&s_lock);
518 
519  if (m_lockHandle > -1)
520  {
521  LOG(VB_GENERAL, LOG_WARNING, LOC + "Already hold delay lock");
522  ReleaseLock();
523  }
524 
525  QStringList types;
526  if (Types.testFlag(FeatureSuspend)) types << "sleep";
527  if (Types.testFlag(FeatureShutdown)) types << "shutdown";
528  if (types.isEmpty())
529  {
530  LOG(VB_GENERAL, LOG_ERR, LOC + "Unknown delay requests");
531  return;
532  }
533 
534  QDBusReply<QDBusUnixFileDescriptor> reply =
535  m_logindInterface->call(QLatin1String("Inhibit"), types.join(":").toLocal8Bit().constData(),
536  QLatin1String("MythTV"), QLatin1String(""), QLatin1String("delay"));
537  if (!reply.isValid())
538  {
539  LOG(VB_GENERAL, LOG_INFO, LOC + QString("Failed to delay %1: %2")
540  .arg(types.join(","), reply.error().message()));
541  m_lockHandle = -1;
542  return;
543  }
544 
545  m_lockHandle = dup(reply.value().fileDescriptor());
546  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Acquired delay FD: %1").arg(m_lockHandle));
547 }
548 
555 {
556  QMutexLocker locker(&s_lock);
557  if (m_lockHandle < 0)
558  return;
559 
560  if (m_scheduledFeature)
562 
563  LOG(VB_GENERAL, LOG_DEBUG, LOC + QString("Releasing delay FD: %1").arg(m_lockHandle));
565  m_lockHandle = -1;
566 }
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:433
MythPowerDBus::DeviceChanged
void DeviceChanged(const QDBusObjectPath &Device)
Update power device state.
Definition: mythpowerdbus.cpp:388
MythPowerDBus::UpdateStatus
bool UpdateStatus(void)
Definition: mythpowerdbus.cpp:217
MythPower::FeatureToString
static QString FeatureToString(enum Feature Type)
Definition: mythpower.cpp:243
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:301
MythPower::FeatureNone
@ FeatureNone
Definition: mythpower.h:41
MythPowerDBus::DidWakeUp
void DidWakeUp(void) override
Definition: mythpowerdbus.cpp:264
MythPower::FeatureIsEquivalent
static bool FeatureIsEquivalent(Feature First, Feature Second)
Definition: mythpower.cpp:257
MythPowerDBus::DBusShuttingDown
void DBusShuttingDown(bool Stopping)
Definition: mythpowerdbus.cpp:201
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:285
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:23
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:33
MythPowerDBus::DeviceAdded
void DeviceAdded(const QDBusObjectPath &Device)
Definition: mythpowerdbus.cpp:359
MythPower::m_maxSupportedDelay
std::chrono::seconds m_maxSupportedDelay
Definition: mythpower.h:103
MythPowerDBus::ReleaseLock
void ReleaseLock(void)
Release our inhibition lock.
Definition: mythpowerdbus.cpp:554
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::ScheduleFeature
virtual bool ScheduleFeature(enum Feature Type, std::chrono::seconds Delay)
Definition: mythpower.cpp:274
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:343
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:515
MythPowerDBus::Changed
void Changed(void)
Definition: mythpowerdbus.cpp:352
LOGIN1_PATH
#define LOGIN1_PATH
Definition: mythpowerdbus.cpp:18
MythPower::FeatureHibernate
@ FeatureHibernate
Definition: mythpower.h:44
MythPower::PowerLevelChanged
void PowerLevelChanged(int Level)
Definition: mythpower.cpp:345
UPOWER_PATH
#define UPOWER_PATH
Definition: mythpowerdbus.cpp:15
MythPowerDBus::DeviceRemoved
void DeviceRemoved(const QDBusObjectPath &Device)
Definition: mythpowerdbus.cpp:371
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:222
MythPower::Feature
Feature
Definition: mythpower.h:39
MythPowerDBus::UpdateProperties
void UpdateProperties(void)
Definition: mythpowerdbus.cpp:399
MythPower::s_lock
static QRecursiveMutex s_lock
Definition: mythpower.h:83
LOGIN1_SERVICE
#define LOGIN1_SERVICE
Definition: mythpowerdbus.cpp:17
MythPowerDBus::RetrieveBatteryLevel
int RetrieveBatteryLevel(const QString &Path)
Definition: mythpowerdbus.cpp:468
MythPower::FeatureRestart
@ FeatureRestart
Definition: mythpower.h:45
UPOWER_INTERFACE
#define UPOWER_INTERFACE
Definition: mythpowerdbus.cpp:16
MythPower::WillShutDown
void WillShutDown(std::chrono::milliseconds MilliSeconds=0ms)
LOGIN1_INTERFACE
#define LOGIN1_INTERFACE
Definition: mythpowerdbus.cpp:19
MythPower::m_featureTimer
QTimer m_featureTimer
Definition: mythpower.h:104
MythPower::FeatureHybridSleep
@ FeatureHybridSleep
Definition: mythpower.h:46
MythPowerDBus::~MythPowerDBus
~MythPowerDBus() override
Definition: mythpowerdbus.cpp:67
MythPower::DidWakeUp
virtual void DidWakeUp(void)
Definition: mythpower.cpp:328
MythPower::UPS
@ UPS
Definition: mythpower.h:30
MythPowerDBus::MythPowerDBus
MythPowerDBus()
Definition: mythpowerdbus.cpp:60
MythPowerDBus::m_logindInterface
QDBusInterface * m_logindInterface
Definition: mythpowerdbus.h:51
LOC
#define LOC
Definition: mythpowerdbus.cpp:8
MythPower::Init
virtual void Init(void)
Definition: mythpower.cpp:130