Merge pull request #4875 from koying/fixdroidremotekeyboard
[vuplus_xbmc] / xbmc / powermanagement / linux / LogindUPowerSyscall.cpp
1 /*
2  *      Copyright (C) 2012 Denis Yantarev
3  *      Copyright (C) 2005-2013 Team XBMC
4  *      http://xbmc.org
5  *
6  *  This Program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2, or (at your option)
9  *  any later version.
10  *
11  *  This Program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with XBMC; see the file COPYING.  If not, write to
18  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
19  *  http://www.gnu.org/copyleft/gpl.html
20  *
21  */
22
23 #include "system.h"
24 #include "LogindUPowerSyscall.h"
25 #include "utils/log.h"
26
27 #ifdef HAS_DBUS
28
29 // logind DBus interface specification:
30 // http://www.freedesktop.org/wiki/Software/Logind/logind
31 //
32 // Inhibitor Locks documentation:
33 // http://www.freedesktop.org/wiki/Software/Logind/inhibit/
34
35 #define LOGIND_DEST  "org.freedesktop.login1"
36 #define LOGIND_PATH  "/org/freedesktop/login1"
37 #define LOGIND_IFACE "org.freedesktop.login1.Manager"
38
39 CLogindUPowerSyscall::CLogindUPowerSyscall()
40 {
41   m_delayLockFd = -1;
42
43   CLog::Log(LOGINFO, "Selected Logind/UPower as PowerSyscall");
44
45   // Check if we have UPower. If not, we avoid any battery related operations.
46   CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices");
47   m_hasUPower = message.SendSystem() != NULL;
48
49   if (!m_hasUPower)
50     CLog::Log(LOGINFO, "LogindUPowerSyscall - UPower not found, battery information will not be available");
51
52   m_canPowerdown = LogindCheckCapability("CanPowerOff");
53   m_canReboot    = LogindCheckCapability("CanReboot");
54   m_canHibernate = LogindCheckCapability("CanHibernate");
55   m_canSuspend   = LogindCheckCapability("CanSuspend");
56
57   InhibitDelayLock();
58
59   m_batteryLevel = 0;
60   if (m_hasUPower)
61     UpdateBatteryLevel();
62
63   DBusError error;
64   dbus_error_init(&error);
65   m_connection = dbus_bus_get_private(DBUS_BUS_SYSTEM, &error);
66
67   if (dbus_error_is_set(&error))
68   {
69     CLog::Log(LOGERROR, "LogindUPowerSyscall: Failed to get dbus connection: %s", error.message);
70     dbus_connection_close(m_connection);
71     dbus_connection_unref(m_connection);
72     m_connection = NULL;
73     dbus_error_free(&error);
74     return;
75   }
76
77   dbus_connection_set_exit_on_disconnect(m_connection, false);
78   dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.login1.Manager',member='PrepareForSleep'", NULL);
79
80   if (m_hasUPower)
81     dbus_bus_add_match(m_connection, "type='signal',interface='org.freedesktop.UPower',member='DeviceChanged'", NULL);
82
83   dbus_connection_flush(m_connection);
84   dbus_error_free(&error);
85 }
86
87 CLogindUPowerSyscall::~CLogindUPowerSyscall()
88 {
89   if (m_connection)
90   {
91     dbus_connection_close(m_connection);
92     dbus_connection_unref(m_connection);
93   }
94
95   ReleaseDelayLock();
96 }
97
98 bool CLogindUPowerSyscall::Powerdown()
99 {
100   return LogindSetPowerState("PowerOff");
101 }
102
103 bool CLogindUPowerSyscall::Reboot()
104 {
105   return LogindSetPowerState("Reboot");
106 }
107
108 bool CLogindUPowerSyscall::Suspend()
109 {
110   return LogindSetPowerState("Suspend");
111 }
112
113 bool CLogindUPowerSyscall::Hibernate()
114 {
115   return LogindSetPowerState("Hibernate");
116 }
117
118 bool CLogindUPowerSyscall::CanPowerdown()
119 {
120   return m_canPowerdown;
121 }
122
123 bool CLogindUPowerSyscall::CanSuspend()
124 {
125   return m_canSuspend;
126 }
127
128 bool CLogindUPowerSyscall::CanHibernate()
129 {
130   return m_canHibernate;
131 }
132
133 bool CLogindUPowerSyscall::CanReboot()
134 {
135   return m_canReboot;
136 }
137
138 bool CLogindUPowerSyscall::HasLogind()
139 {
140   // recommended method by systemd devs. The seats directory
141   // doesn't exist unless logind created it and therefore is running.
142   // see also https://mail.gnome.org/archives/desktop-devel-list/2013-March/msg00092.html
143   return (access("/run/systemd/seats/", F_OK) >= 0);
144 }
145
146 bool CLogindUPowerSyscall::LogindSetPowerState(const char *state)
147 {
148   CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, state);
149   // The user_interaction boolean parameters can be used to control
150   // wether PolicyKit should interactively ask the user for authentication
151   // credentials if it needs to.
152   message.AppendArgument(false);
153   return message.SendSystem() != NULL;
154 }
155
156 bool CLogindUPowerSyscall::LogindCheckCapability(const char *capability)
157 {
158   char *arg;
159   CDBusMessage message(LOGIND_DEST, LOGIND_PATH, LOGIND_IFACE, capability);
160   DBusMessage *reply = message.SendSystem();
161   if(reply && dbus_message_get_args(reply, NULL, DBUS_TYPE_STRING, &arg, DBUS_TYPE_INVALID))
162   {
163     // Returns one of "yes", "no" or "challenge". If "challenge" is
164     // returned the operation is available, but only after authorization.
165     return (strcmp(arg, "yes") == 0);
166   }
167   return false;
168 }
169
170 int CLogindUPowerSyscall::BatteryLevel()
171 {
172   return m_batteryLevel;
173 }
174
175 void CLogindUPowerSyscall::UpdateBatteryLevel()
176 {
177   char** source  = NULL;
178   int    length = 0;
179   double batteryLevelSum = 0;
180   int    batteryCount = 0;
181
182   CDBusMessage message("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "EnumerateDevices");
183   DBusMessage *reply = message.SendSystem();
184
185   if (!reply)
186     return;
187
188   if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_ARRAY, DBUS_TYPE_OBJECT_PATH, &source, &length, DBUS_TYPE_INVALID))
189   {
190     CLog::Log(LOGWARNING, "LogindUPowerSyscall: failed to enumerate devices");
191     return;
192   }
193
194   for (int i = 0; i < length; i++)
195   {
196     CVariant properties = CDBusUtil::GetAll("org.freedesktop.UPower", source[i], "org.freedesktop.UPower.Device");
197     bool isRechargeable = properties["IsRechargeable"].asBoolean();
198
199     if (isRechargeable)
200     {
201       batteryCount++;
202       batteryLevelSum += properties["Percentage"].asDouble();
203     }
204   }
205
206   dbus_free_string_array(source);
207
208   if (batteryCount > 0)
209     m_batteryLevel = (int)(batteryLevelSum / (double)batteryCount);
210
211   m_lowBattery = CDBusUtil::GetVariant("org.freedesktop.UPower", "/org/freedesktop/UPower", "org.freedesktop.UPower", "OnLowBattery").asBoolean();
212 }
213
214 bool CLogindUPowerSyscall::PumpPowerEvents(IPowerEventsCallback *callback)
215 {
216   bool result = false;
217   bool releaseLock = false;
218
219   if (m_connection)
220   {
221     dbus_connection_read_write(m_connection, 0);
222     DBusMessage *msg = dbus_connection_pop_message(m_connection);
223
224     if (msg)
225     {
226       if (dbus_message_is_signal(msg, "org.freedesktop.login1.Manager", "PrepareForSleep"))
227       {
228         dbus_bool_t arg;
229         // the boolean argument defines whether we are going to sleep (true) or just woke up (false)
230         dbus_message_get_args(msg, NULL, DBUS_TYPE_BOOLEAN, &arg, DBUS_TYPE_INVALID);
231         CLog::Log(LOGDEBUG, "LogindUPowerSyscall: Received PrepareForSleep with arg %i", (int)arg);
232         if (arg)
233         {
234           callback->OnSleep();
235           releaseLock = true;
236         }
237         else
238         {
239           callback->OnWake();
240           InhibitDelayLock();
241         }
242
243         result = true;
244       }
245       else if (dbus_message_is_signal(msg, "org.freedesktop.UPower", "DeviceChanged"))
246       {
247         bool lowBattery = m_lowBattery;
248         UpdateBatteryLevel();
249         if (m_lowBattery && !lowBattery)
250           callback->OnLowBattery();
251
252         result = true;
253       }
254       else
255         CLog::Log(LOGDEBUG, "LogindUPowerSyscall - Received unknown signal %s", dbus_message_get_member(msg));
256
257       dbus_message_unref(msg);
258     }
259   }
260
261   if (releaseLock)
262     ReleaseDelayLock();
263
264   return result;
265 }
266
267 void CLogindUPowerSyscall::InhibitDelayLock()
268 {
269 #ifdef DBUS_TYPE_UNIX_FD
270   CDBusMessage message("org.freedesktop.login1", "/org/freedesktop/login1", "org.freedesktop.login1.Manager", "Inhibit");
271   message.AppendArgument("sleep"); // what to inhibit
272   message.AppendArgument("XBMC"); // who
273   message.AppendArgument(""); // reason
274   message.AppendArgument("delay"); // mode
275
276   DBusMessage *reply = message.SendSystem();
277
278   if (!reply)
279   {
280     CLog::Log(LOGWARNING, "LogindUPowerSyscall - failed to inhibit sleep delay lock");
281     m_delayLockFd = -1;
282     return;
283   }
284
285   if (!dbus_message_get_args(reply, NULL, DBUS_TYPE_UNIX_FD, &m_delayLockFd, DBUS_TYPE_INVALID))
286   {
287     CLog::Log(LOGWARNING, "LogindUPowerSyscall - failed to get inhibit file descriptor");
288     m_delayLockFd = -1;
289     return;
290   }
291
292     CLog::Log(LOGDEBUG, "LogindUPowerSyscall - inhibit lock taken, fd %i", m_delayLockFd);
293 #else
294     CLog::Log(LOGWARNING, "LogindUPowerSyscall - inhibit lock support not compiled in");
295 #endif
296 }
297
298 void CLogindUPowerSyscall::ReleaseDelayLock()
299 {
300   if (m_delayLockFd != -1)
301   {
302     close(m_delayLockFd);
303     m_delayLockFd = -1;
304     CLog::Log(LOGDEBUG, "LogindUPowerSyscall - delay lock released");
305   }
306 }
307
308 #endif