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