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