Unity 8
AccountsService.cpp
1 /*
2  * Copyright (C) 2013-2016 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_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
40 #define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
41 #define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
42 #define PROP_FAILED_FINGERPRINT_LOGINS QStringLiteral("FailedFingerprintLogins")
43 #define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
44 #define PROP_INPUT_SOURCES QStringLiteral("InputSources")
45 #define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
46 #define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
47 #define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
48 #define PROP_MOUSE_DOUBLE_CLICK_SPEED QStringLiteral("MouseDoubleClickSpeed")
49 #define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
50 #define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
51 #define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
52 #define PROP_REAL_NAME QStringLiteral("RealName")
53 #define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
54 #define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
55 #define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
56 #define PROP_TOUCHPAD_DISABLE_WITH_MOUSE QStringLiteral("TouchpadDisableWithMouse")
57 #define PROP_TOUCHPAD_DOUBLE_CLICK_SPEED QStringLiteral("TouchpadDoubleClickSpeed")
58 #define PROP_TOUCHPAD_PRIMARY_BUTTON QStringLiteral("TouchpadPrimaryButton")
59 #define PROP_TOUCHPAD_SCROLL_SPEED QStringLiteral("TouchpadScrollSpeed")
60 #define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
61 #define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
62 
63 using StringMap = QMap<QString,QString>;
64 using StringMapList = QList<StringMap>;
65 Q_DECLARE_METATYPE(StringMapList)
66 
67 
68 QVariant primaryButtonConverter(const QVariant &value)
69 {
70  QString stringValue = value.toString();
71  if (stringValue == QLatin1String("left")) {
72  return QVariant::fromValue(0);
73  } else if (stringValue == QLatin1String("right")) {
74  return QVariant::fromValue(1); // Mir is less clear on this -- any non-zero value is the same
75  } else {
76  return QVariant::fromValue(0); // default to left
77  }
78 }
79 
80 AccountsService::AccountsService(QObject* parent, const QString &user)
81  : QObject(parent)
82  , m_service(new AccountsServiceDBusAdaptor(this))
83 {
84  m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
85  QStringLiteral("/com/canonical/Unity/Input"),
86  QStringLiteral("com.canonical.Unity.Input"),
87  QDBusConnection::SM_BUSNAME(), this);
88 
89  connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
90  connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
91 
92  registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
93  registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
94  registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
95  registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
96  registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
97  registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
98  registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION, QStringLiteral("enableFingerprintIdentificationChanged"));
99  registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
100  registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
101  registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
102  registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
103  registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
104  registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
105  registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
106  registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
107 
108  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
109  m_unityInput, QStringLiteral("setMouseCursorSpeed"));
110  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
111  m_unityInput, QStringLiteral("setMouseDoubleClickSpeed"));
112  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
113  m_unityInput, QStringLiteral("setMousePrimaryButton"),
114  primaryButtonConverter);
115  registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
116  m_unityInput, QStringLiteral("setMouseScrollSpeed"));
117  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
118  m_unityInput, QStringLiteral("setTouchpadCursorSpeed"));
119  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
120  m_unityInput, QStringLiteral("setTouchpadScrollSpeed"));
121  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
122  m_unityInput, QStringLiteral("setTouchpadDisableWhileTyping"));
123  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
124  m_unityInput, QStringLiteral("setTouchpadDisableWithMouse"));
125  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
126  m_unityInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
127  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
128  m_unityInput, QStringLiteral("setTouchpadPrimaryButton"),
129  primaryButtonConverter);
130  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
131  m_unityInput, QStringLiteral("setTouchpadTapToClick"));
132  registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
133  m_unityInput, QStringLiteral("setTouchpadTwoFingerScroll"));
134 
135  setUser(!user.isEmpty() ? user : QString::fromUtf8(g_get_user_name()));
136 }
137 
138 QString AccountsService::user() const
139 {
140  return m_user;
141 }
142 
143 void AccountsService::setUser(const QString &user)
144 {
145  if (user.isEmpty() || m_user == user)
146  return;
147 
148  bool wasEmpty = m_user.isEmpty();
149 
150  m_user = user;
151  Q_EMIT userChanged();
152 
153  // Do the first update synchronously, as a cheap way to block rendering
154  // until we have the right values on bootup.
155  refresh(!wasEmpty);
156 }
157 
158 bool AccountsService::demoEdges() const
159 {
160  auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES);
161  return value.toBool();
162 }
163 
164 void AccountsService::setDemoEdges(bool demoEdges)
165 {
166  setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
167 }
168 
169 QStringList AccountsService::demoEdgesCompleted() const
170 {
171  auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
172  return value.toStringList();
173 }
174 
175 void AccountsService::markDemoEdgeCompleted(const QString &edge)
176 {
177  auto currentList = demoEdgesCompleted();
178  if (!currentList.contains(edge)) {
179  setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
180  }
181 }
182 
183 bool AccountsService::enableFingerprintIdentification() const
184 {
185  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION);
186  return value.toBool();
187 }
188 
189 bool AccountsService::enableLauncherWhileLocked() const
190 {
191  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
192  return value.toBool();
193 }
194 
195 bool AccountsService::enableIndicatorsWhileLocked() const
196 {
197  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
198  return value.toBool();
199 }
200 
201 QString AccountsService::backgroundFile() const
202 {
203  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
204  return value.toString();
205 }
206 
207 bool AccountsService::statsWelcomeScreen() const
208 {
209  auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
210  return value.toBool();
211 }
212 
213 AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
214 {
215  auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
216  return (PasswordDisplayHint)value.toInt();
217 }
218 
219 bool AccountsService::hereEnabled() const
220 {
221  auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED);
222  return value.toBool();
223 }
224 
225 void AccountsService::setHereEnabled(bool enabled)
226 {
227  setProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, enabled);
228 }
229 
230 QString AccountsService::hereLicensePath() const
231 {
232  auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
233  QString hereLicensePath = value.toString();
234  if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
235  hereLicensePath = QStringLiteral("");
236  return hereLicensePath;
237 }
238 
239 bool AccountsService::hereLicensePathValid() const
240 {
241  auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
242  return !value.toString().isNull();
243 }
244 
245 QString AccountsService::realName() const
246 {
247  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
248  return value.toString();
249 }
250 
251 void AccountsService::setRealName(const QString &realName)
252 {
253  setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
254 }
255 
256 QString AccountsService::email() const
257 {
258  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
259  return value.toString();
260 }
261 
262 void AccountsService::setEmail(const QString &email)
263 {
264  setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
265 }
266 
267 QStringList AccountsService::keymaps() const
268 {
269  auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES);
270  QDBusArgument arg = value.value<QDBusArgument>();
271  StringMapList maps = qdbus_cast<StringMapList>(arg);
272  QStringList simplifiedMaps;
273 
274  Q_FOREACH(const StringMap &map, maps) {
275  Q_FOREACH(const QString &entry, map) {
276  simplifiedMaps.append(entry);
277  }
278  }
279 
280  if (!simplifiedMaps.isEmpty()) {
281  return simplifiedMaps;
282  }
283 
284  return {QStringLiteral("us")};
285 }
286 
287 void AccountsService::setKeymaps(const QStringList &keymaps)
288 {
289  if (keymaps.isEmpty()) {
290  qWarning() << "Setting empty keymaps is not supported";
291  return;
292  }
293 
294  StringMapList result;
295  Q_FOREACH(const QString &keymap, keymaps) {
296  StringMap map;
297  map.insert(QStringLiteral("xkb"), keymap);
298  result.append(map);
299  }
300 
301  setProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QVariant::fromValue(result));
302  Q_EMIT keymapsChanged();
303 }
304 
305 uint AccountsService::failedFingerprintLogins() const
306 {
307  return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
308 }
309 
310 void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
311 {
312  setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, failedFingerprintLogins);
313 }
314 
315 uint AccountsService::failedLogins() const
316 {
317  return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
318 }
319 
320 void AccountsService::setFailedLogins(uint failedLogins)
321 {
322  setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
323 }
324 
325 // ====================================================
326 // Everything below this line is generic helper methods
327 // ====================================================
328 
329 void AccountsService::emitChangedForProperty(const QString &interface, const QString &property)
330 {
331  QString signalName = m_properties[interface][property].signal;
332  QMetaObject::invokeMethod(this, signalName.toUtf8().data());
333 }
334 
335 QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
336 {
337  return m_properties[interface][property].value;
338 }
339 
340 void AccountsService::setProperty(const QString &interface, const QString &property, const QVariant &value)
341 {
342  if (m_properties[interface][property].value != value) {
343  m_properties[interface][property].value = value;
344  m_service->setUserPropertyAsync(m_user, interface, property, value);
345  emitChangedForProperty(interface, property);
346  }
347 }
348 
349 void AccountsService::updateCache(const QString &interface, const QString &property, const QVariant &value)
350 {
351  PropertyInfo &info = m_properties[interface][property];
352 
353  if (info.proxyInterface) {
354  QVariant finalValue;
355  if (info.proxyConverter) {
356  finalValue = info.proxyConverter(value);
357  } else {
358  finalValue = value;
359  }
360  info.proxyInterface->asyncCall(info.proxyMethod, finalValue);
361  return; // don't bother saving a copy
362  }
363 
364  if (info.value != value) {
365  info.value = value;
366  emitChangedForProperty(interface, property);
367  }
368 }
369 
370 void AccountsService::updateProperty(const QString &interface, const QString &property)
371 {
372  QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
373  interface,
374  property);
375  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
376 
377  connect(watcher, &QDBusPendingCallWatcher::finished,
378  this, [this, interface, property](QDBusPendingCallWatcher* watcher) {
379 
380  QDBusPendingReply<QVariant> reply = *watcher;
381  watcher->deleteLater();
382  if (reply.isError()) {
383  qWarning() << "Failed to get '" << property << "' property:" << reply.error().message();
384  return;
385  }
386 
387  updateCache(interface, property, reply.value());
388  });
389 }
390 
391 void AccountsService::updateAllProperties(const QString &interface, bool async)
392 {
393  QDBusPendingCall pendingReply = m_service->getAllPropertiesAsync(m_user,
394  interface);
395  QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
396 
397  connect(watcher, &QDBusPendingCallWatcher::finished,
398  this, [this, interface](QDBusPendingCallWatcher* watcher) {
399 
400  QDBusPendingReply< QHash<QString, QVariant> > reply = *watcher;
401  watcher->deleteLater();
402  if (reply.isError()) {
403  qWarning() << "Failed to get all properties for" << interface << ":" << reply.error().message();
404  return;
405  }
406 
407  auto valueHash = reply.value();
408  auto i = valueHash.constBegin();
409  while (i != valueHash.constEnd()) {
410  updateCache(interface, i.key(), i.value());
411  ++i;
412  }
413  });
414  if (!async) {
415  watcher->waitForFinished();
416  }
417 }
418 
419 void AccountsService::registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter)
420 {
421  registerProperty(interface, property, nullptr);
422 
423  m_properties[interface][property].proxyInterface = iface;
424  m_properties[interface][property].proxyMethod = method;
425  m_properties[interface][property].proxyConverter = converter;
426 }
427 
428 void AccountsService::registerProperty(const QString &interface, const QString &property, const QString &signal)
429 {
430  m_properties[interface][property] = PropertyInfo();
431  m_properties[interface][property].signal = signal;
432 }
433 
434 void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
435 {
436  if (m_user != user) {
437  return;
438  }
439 
440  auto propHash = m_properties.value(interface);
441  auto i = propHash.constBegin();
442  while (i != propHash.constEnd()) {
443  if (changed.contains(i.key())) {
444  updateProperty(interface, i.key());
445  }
446  ++i;
447  }
448 }
449 
450 void AccountsService::onMaybeChanged(const QString &user)
451 {
452  if (m_user != user) {
453  return;
454  }
455 
456  // Any of the standard properties might have changed!
457  auto propHash = m_properties.value(IFACE_ACCOUNTS_USER);
458  auto i = propHash.constBegin();
459  while (i != propHash.constEnd()) {
460  updateProperty(IFACE_ACCOUNTS_USER, i.key());
461  ++i;
462  }
463 }
464 
465 void AccountsService::refresh(bool async)
466 {
467  auto i = m_properties.constBegin();
468  while (i != m_properties.constEnd()) {
469  updateAllProperties(i.key(), async);
470  ++i;
471  }
472 }