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