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