Unity 8
AccountsService.cpp
1 /*
2  * Copyright (C) 2013, 2015 Canonical, Ltd.
3  *
4  * This program is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation; version 3.
7  *
8  * This program is distributed in the hope that it will be useful,
9  * but WITHOUT ANY WARRANTY; without even the implied warranty of
10  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11  * GNU General Public License for more details.
12  *
13  * You should have received a copy of the GNU General Public License
14  * along with this program. If not, see <http://www.gnu.org/licenses/>.
15  */
16 
17 #include "AccountsService.h"
18 #include "AccountsServiceDBusAdaptor.h"
19 
20 #include <QDBusInterface>
21 #include <QFile>
22 #include <QStringList>
23 #include <QDebug>
24 
25 #include <glib.h>
26 
27 #define IFACE_ACCOUNTS_USER QStringLiteral("org.freedesktop.Accounts.User")
28 #define IFACE_LOCATION_HERE QStringLiteral("com.ubuntu.location.providers.here.AccountsService")
29 #define IFACE_UBUNTU_INPUT QStringLiteral("com.ubuntu.AccountsService.Input")
30 #define IFACE_UBUNTU_SECURITY QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy")
31 #define IFACE_UBUNTU_SECURITY_OLD QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy")
32 #define IFACE_UNITY QStringLiteral("com.canonical.unity.AccountsService")
33 #define IFACE_UNITY_PRIVATE QStringLiteral("com.canonical.unity.AccountsService.Private")
34 
35 #define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
36 #define PROP_DEMO_EDGES QStringLiteral("demo-edges")
37 #define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
38 #define PROP_EMAIL QStringLiteral("Email")
39 #define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
40 #define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
41 #define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
42 #define PROP_INPUT_SOURCES QStringLiteral("InputSources")
43 #define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
44 #define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
45 #define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
46 #define PROP_MOUSE_DOUBLE_CLICK_SPEED QStringLiteral("MouseDoubleClickSpeed")
47 #define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
48 #define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
49 #define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
50 #define PROP_REAL_NAME QStringLiteral("RealName")
51 #define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
52 #define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
53 #define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
54 #define PROP_TOUCHPAD_DISABLE_WITH_MOUSE QStringLiteral("TouchpadDisableWithMouse")
55 #define PROP_TOUCHPAD_DOUBLE_CLICK_SPEED QStringLiteral("TouchpadDoubleClickSpeed")
56 #define PROP_TOUCHPAD_PRIMARY_BUTTON QStringLiteral("TouchpadPrimaryButton")
57 #define PROP_TOUCHPAD_SCROLL_SPEED QStringLiteral("TouchpadScrollSpeed")
58 #define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
59 #define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
60 
61 using StringMap = QMap<QString,QString>;
62 using StringMapList = QList<StringMap>;
63 Q_DECLARE_METATYPE(StringMapList)
64 
65 
66 QVariant primaryButtonConverter(const QVariant &value)
67 {
68  QString stringValue = value.toString();
69  if (stringValue == "left") {
70  return QVariant::fromValue(0);
71  } else if (stringValue == "right") {
72  return QVariant::fromValue(1); // Mir is less clear on this -- any non-zero value is the same
73  } else {
74  return QVariant::fromValue(0); // default to left
75  }
76 }
77 
78 AccountsService::AccountsService(QObject* parent, const QString &user)
79  : QObject(parent)
80  , m_service(new AccountsServiceDBusAdaptor(this))
81 {
82  m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
83  QStringLiteral("/com/canonical/Unity/Input"),
84  QStringLiteral("com.canonical.Unity.Input"),
85  QDBusConnection::SM_BUSNAME(), this);
86 
87  connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
88  connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
89 
90  registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
91  registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
92  registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
93  registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
94  registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
95  registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
96  registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
97  registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
98  registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
99  registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
100  registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
101  registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
102  registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
103 
104  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
105  m_unityInput, QStringLiteral("setMouseCursorSpeed"));
106  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
107  m_unityInput, QStringLiteral("setMouseDoubleClickSpeed"));
108  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
109  m_unityInput, QStringLiteral("setMousePrimaryButton"),
110  primaryButtonConverter);
111  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
112  m_unityInput, QStringLiteral("setMouseScrollSpeed"));
113  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
114  m_unityInput, QStringLiteral("setTouchpadCursorSpeed"));
115  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
116  m_unityInput, QStringLiteral("setTouchpadScrollSpeed"));
117  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
118  m_unityInput, QStringLiteral("setTouchpadDisableWhileTyping"));
119  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
120  m_unityInput, QStringLiteral("setTouchpadDisableWithMouse"));
121  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
122  m_unityInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
123  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
124  m_unityInput, QStringLiteral("setTouchpadPrimaryButton"),
125  primaryButtonConverter);
126  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
127  m_unityInput, QStringLiteral("setTouchpadTapToClick"));
128  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
129  m_unityInput, QStringLiteral("setTouchpadTwoFingerScroll"));
130 
131  setUser(!user.isEmpty() ? user : QString::fromUtf8(g_get_user_name()));
132 }
133 
134 QString AccountsService::user() const
135 {
136  return m_user;
137 }
138 
139 void AccountsService::setUser(const QString &user)
140 {
141  if (user.isEmpty() || m_user == user)
142  return;
143 
144  bool wasEmpty = m_user.isEmpty();
145 
146  m_user = user;
147  Q_EMIT userChanged();
148 
149  // Do the first update synchronously, as a cheap way to block rendering
150  // until we have the right values on bootup.
151  refresh(!wasEmpty);
152 }
153 
154 bool AccountsService::demoEdges() const
155 {
156  auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES);
157  return value.toBool();
158 }
159 
160 void AccountsService::setDemoEdges(bool demoEdges)
161 {
162  setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
163 }
164 
165 QStringList AccountsService::demoEdgesCompleted() const
166 {
167  auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
168  return value.toStringList();
169 }
170 
171 void AccountsService::markDemoEdgeCompleted(const QString &edge)
172 {
173  auto currentList = demoEdgesCompleted();
174  if (!currentList.contains(edge)) {
175  setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
176  }
177 }
178 
179 bool AccountsService::enableLauncherWhileLocked() const
180 {
181  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
182  return value.toBool();
183 }
184 
185 bool AccountsService::enableIndicatorsWhileLocked() const
186 {
187  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
188  return value.toBool();
189 }
190 
191 QString AccountsService::backgroundFile() const
192 {
193  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
194  return value.toString();
195 }
196 
197 bool AccountsService::statsWelcomeScreen() const
198 {
199  auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
200  return value.toBool();
201 }
202 
203 AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
204 {
205  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
206  return (PasswordDisplayHint)value.toInt();
207 }
208 
209 bool AccountsService::hereEnabled() const
210 {
211  auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED);
212  return value.toBool();
213 }
214 
215 void AccountsService::setHereEnabled(bool enabled)
216 {
217  setProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, enabled);
218 }
219 
220 QString AccountsService::hereLicensePath() const
221 {
222  auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
223  QString hereLicensePath = value.toString();
224  if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
225  hereLicensePath = QStringLiteral("");
226  return hereLicensePath;
227 }
228 
229 bool AccountsService::hereLicensePathValid() const
230 {
231  auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
232  return !value.toString().isNull();
233 }
234 
235 QString AccountsService::realName() const
236 {
237  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
238  return value.toString();
239 }
240 
241 void AccountsService::setRealName(const QString &realName)
242 {
243  setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
244 }
245 
246 QString AccountsService::email() const
247 {
248  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
249  return value.toString();
250 }
251 
252 void AccountsService::setEmail(const QString &email)
253 {
254  setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
255 }
256 
257 QStringList AccountsService::keymaps() const
258 {
259  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES);
260  QDBusArgument arg = value.value<QDBusArgument>();
261  StringMapList maps = qdbus_cast<StringMapList>(arg);
262  QStringList simplifiedMaps;
263 
264  Q_FOREACH(const StringMap &map, maps) {
265  Q_FOREACH(const QString &entry, map) {
266  simplifiedMaps.append(entry);
267  }
268  }
269 
270  if (!simplifiedMaps.isEmpty()) {
271  return simplifiedMaps;
272  }
273 
274  return {QStringLiteral("us")};
275 }
276 
277 uint AccountsService::failedLogins() const
278 {
279  return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
280 }
281 
282 void AccountsService::setFailedLogins(uint failedLogins)
283 {
284  setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
285 }
286 
287 // ====================================================
288 // Everything below this line is generic helper methods
289 // ====================================================
290 
291 void AccountsService::emitChangedForProperty(const QString &interface, const QString &property)
292 {
293  QString signalName = m_properties[interface][property].signal;
294  QMetaObject::invokeMethod(this, signalName.toUtf8().data());
295 }
296 
297 QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
298 {
299  return m_properties[interface][property].value;
300 }
301 
302 void AccountsService::setProperty(const QString &interface, const QString &property, const QVariant &value)
303 {
304  if (m_properties[interface][property].value != value) {
305  m_properties[interface][property].value = value;
306  m_service->setUserPropertyAsync(m_user, interface, property, value);
307  emitChangedForProperty(interface, property);
308  }
309 }
310 
311 void AccountsService::updateCache(const QString &interface, const QString &property, const QVariant &value)
312 {
313  PropertyInfo &info = m_properties[interface][property];
314 
315  if (info.proxyInterface) {
316  QVariant finalValue;
317  if (info.proxyConverter) {
318  finalValue = info.proxyConverter(value);
319  } else {
320  finalValue = value;
321  }
322  info.proxyInterface->asyncCall(info.proxyMethod, finalValue);
323  return; // don't bother saving a copy
324  }
325 
326  if (info.value != value) {
327  info.value = value;
328  emitChangedForProperty(interface, property);
329  }
330 }
331 
332 void AccountsService::updateProperty(const QString &interface, const QString &property)
333 {
334  QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
335  interface,
336  property);
337  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
338 
339  connect(watcher, &QDBusPendingCallWatcher::finished,
340  this, [this, interface, property](QDBusPendingCallWatcher* watcher) {
341 
342  QDBusPendingReply<QVariant> reply = *watcher;
343  watcher->deleteLater();
344  if (reply.isError()) {
345  qWarning() << "Failed to get '" << property << "' property:" << reply.error().message();
346  return;
347  }
348 
349  updateCache(interface, property, reply.value());
350  });
351 }
352 
353 void AccountsService::updateAllProperties(const QString &interface, bool async)
354 {
355  QDBusPendingCall pendingReply = m_service->getAllPropertiesAsync(m_user,
356  interface);
357  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
358 
359  connect(watcher, &QDBusPendingCallWatcher::finished,
360  this, [this, interface](QDBusPendingCallWatcher* watcher) {
361 
362  QDBusPendingReply< QHash<QString, QVariant> > reply = *watcher;
363  watcher->deleteLater();
364  if (reply.isError()) {
365  qWarning() << "Failed to get all properties for" << interface << ":" << reply.error().message();
366  return;
367  }
368 
369  auto valueHash = reply.value();
370  auto i = valueHash.constBegin();
371  while (i != valueHash.constEnd()) {
372  updateCache(interface, i.key(), i.value());
373  ++i;
374  }
375  });
376  if (!async) {
377  watcher->waitForFinished();
378  }
379 }
380 
381 void AccountsService::registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter)
382 {
383  registerProperty(interface, property, nullptr);
384 
385  m_properties[interface][property].proxyInterface = iface;
386  m_properties[interface][property].proxyMethod = method;
387  m_properties[interface][property].proxyConverter = converter;
388 }
389 
390 void AccountsService::registerProperty(const QString &interface, const QString &property, const QString &signal)
391 {
392  m_properties[interface][property] = PropertyInfo();
393  m_properties[interface][property].signal = signal;
394 }
395 
396 void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
397 {
398  if (m_user != user) {
399  return;
400  }
401 
402  auto propHash = m_properties.value(interface);
403  auto i = propHash.constBegin();
404  while (i != propHash.constEnd()) {
405  if (changed.contains(i.key())) {
406  updateProperty(interface, i.key());
407  }
408  ++i;
409  }
410 }
411 
412 void AccountsService::onMaybeChanged(const QString &user)
413 {
414  if (m_user != user) {
415  return;
416  }
417 
418  // Any of the standard properties might have changed!
419  auto propHash = m_properties.value(IFACE_ACCOUNTS_USER);
420  auto i = propHash.constBegin();
421  while (i != propHash.constEnd()) {
422  updateProperty(IFACE_ACCOUNTS_USER, i.key());
423  ++i;
424  }
425 }
426 
427 void AccountsService::refresh(bool async)
428 {
429  auto i = m_properties.constBegin();
430  while (i != m_properties.constEnd()) {
431  updateAllProperties(i.key(), async);
432  ++i;
433  }
434 }