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
65using StringMap = QMap<QString,QString>;
66using StringMapList = QList<StringMap>;
67Q_DECLARE_METATYPE(StringMapList)
68
69
70QVariant 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
82AccountsService::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
141QString AccountsService::user() const
142{
143 return m_user;
144}
145
146void 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
161bool AccountsService::demoEdges() const
162{
163 auto value = getProperty(IFACE_LOMIRI, PROP_DEMO_EDGES);
164 return value.toBool();
165}
166
167void AccountsService::setDemoEdges(bool demoEdges)
168{
169 setProperty(IFACE_LOMIRI, PROP_DEMO_EDGES, demoEdges);
170}
171
172QStringList AccountsService::demoEdgesCompleted() const
173{
174 auto value = getProperty(IFACE_LOMIRI, PROP_DEMO_EDGES_COMPLETED);
175 return value.toStringList();
176}
177
178void 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
186bool AccountsService::enableFingerprintIdentification() const
187{
188 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION);
189 return value.toBool();
190}
191
192bool AccountsService::enableLauncherWhileLocked() const
193{
194 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
195 return value.toBool();
196}
197
198bool AccountsService::enableIndicatorsWhileLocked() const
199{
200 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
201 return value.toBool();
202}
203
204QString AccountsService::backgroundFile() const
205{
206 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
207 return value.toString();
208}
209
210bool AccountsService::statsWelcomeScreen() const
211{
212 auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
213 return value.toBool();
214}
215
216AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
217{
218 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
219 return (PasswordDisplayHint)value.toInt();
220}
221
222QString 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
241QString AccountsService::defaultPinCodePromptManager() const
242{
243 return m_defaultPinPromptManager;
244}
245
246uint AccountsService::pincodeLength() const
247{
248 return getProperty(IFACE_UBUNTU_SECURITY, PROP_PINCODE_LENGTH).toUInt();
249}
250
251QString AccountsService::realName() const
252{
253 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
254 return value.toString();
255}
256
257void AccountsService::setRealName(const QString &realName)
258{
259 setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
260}
261
262QString AccountsService::email() const
263{
264 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
265 return value.toString();
266}
267
268void AccountsService::setEmail(const QString &email)
269{
270 setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
271}
272
273QStringList 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
293void 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
311uint AccountsService::failedFingerprintLogins() const
312{
313 return getProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
314}
315
316void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
317{
318 setProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, failedFingerprintLogins);
319}
320
321uint AccountsService::failedLogins() const
322{
323 return getProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_LOGINS).toUInt();
324}
325
326void 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
335void 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
341QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
342{
343 return m_properties[interface][property].value;
344}
345
346void 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
355void 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
376void 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
397void 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
425void 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
434void 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
440void 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
456void 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
471void 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}