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
27#define IFACE_ACCOUNTS_USER QStringLiteral("org.freedesktop.Accounts.User")
28#define IFACE_UBUNTU_INPUT QStringLiteral("com.lomiri.AccountsService.Input")
29#define IFACE_UBUNTU_SECURITY QStringLiteral("com.lomiri.AccountsService.SecurityPrivacy")
30#define IFACE_UBUNTU_SECURITY_OLD QStringLiteral("com.lomiri.touch.AccountsService.SecurityPrivacy")
31#define IFACE_LOMIRI QStringLiteral("com.lomiri.shell.AccountsService")
32#define IFACE_LOMIRI_PRIVATE QStringLiteral("com.lomiri.shell.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
62using StringMap = QMap<QString,QString>;
63using StringMapList = QList<StringMap>;
64Q_DECLARE_METATYPE(StringMapList)
65
66
67QVariant 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
79AccountsService::AccountsService(QObject* parent, const QString &user)
80 : QObject(parent)
81 , m_service(new AccountsServiceDBusAdaptor(this))
82{
83 m_syscompInput = new QDBusInterface(QStringLiteral("com.lomiri.SystemCompositor.Input"),
84 QStringLiteral("/com/lomiri/SystemCompositor/Input"),
85 QStringLiteral("com.lomiri.SystemCompositor.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_LOMIRI, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
101 registerProperty(IFACE_LOMIRI, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
102 registerProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
103 registerProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
104
105 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
106 m_syscompInput, QStringLiteral("setMouseCursorSpeed"));
107 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
108 m_syscompInput, QStringLiteral("setMouseDoubleClickSpeed"));
109 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
110 m_syscompInput, QStringLiteral("setMousePrimaryButton"),
111 primaryButtonConverter);
112 registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
113 m_syscompInput, QStringLiteral("setMouseScrollSpeed"));
114 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
115 m_syscompInput, QStringLiteral("setTouchpadCursorSpeed"));
116 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
117 m_syscompInput, QStringLiteral("setTouchpadScrollSpeed"));
118 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
119 m_syscompInput, QStringLiteral("setTouchpadDisableWhileTyping"));
120 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
121 m_syscompInput, QStringLiteral("setTouchpadDisableWithMouse"));
122 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
123 m_syscompInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
124 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
125 m_syscompInput, QStringLiteral("setTouchpadPrimaryButton"),
126 primaryButtonConverter);
127 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
128 m_syscompInput, QStringLiteral("setTouchpadTapToClick"));
129 registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
130 m_syscompInput, QStringLiteral("setTouchpadTwoFingerScroll"));
131
132 setUser(!user.isEmpty() ? user : QString::fromUtf8(g_get_user_name()));
133}
134
135QString AccountsService::user() const
136{
137 return m_user;
138}
139
140void 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
155bool AccountsService::demoEdges() const
156{
157 auto value = getProperty(IFACE_LOMIRI, PROP_DEMO_EDGES);
158 return value.toBool();
159}
160
161void AccountsService::setDemoEdges(bool demoEdges)
162{
163 setProperty(IFACE_LOMIRI, PROP_DEMO_EDGES, demoEdges);
164}
165
166QStringList AccountsService::demoEdgesCompleted() const
167{
168 auto value = getProperty(IFACE_LOMIRI, PROP_DEMO_EDGES_COMPLETED);
169 return value.toStringList();
170}
171
172void AccountsService::markDemoEdgeCompleted(const QString &edge)
173{
174 auto currentList = demoEdgesCompleted();
175 if (!currentList.contains(edge)) {
176 setProperty(IFACE_LOMIRI, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
177 }
178}
179
180bool AccountsService::enableFingerprintIdentification() const
181{
182 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION);
183 return value.toBool();
184}
185
186bool AccountsService::enableLauncherWhileLocked() const
187{
188 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
189 return value.toBool();
190}
191
192bool AccountsService::enableIndicatorsWhileLocked() const
193{
194 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
195 return value.toBool();
196}
197
198QString AccountsService::backgroundFile() const
199{
200 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
201 return value.toString();
202}
203
204bool AccountsService::statsWelcomeScreen() const
205{
206 auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
207 return value.toBool();
208}
209
210AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
211{
212 auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
213 return (PasswordDisplayHint)value.toInt();
214}
215
216QString AccountsService::realName() const
217{
218 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
219 return value.toString();
220}
221
222void AccountsService::setRealName(const QString &realName)
223{
224 setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
225}
226
227QString AccountsService::email() const
228{
229 auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
230 return value.toString();
231}
232
233void AccountsService::setEmail(const QString &email)
234{
235 setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
236}
237
238QStringList 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
258void 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
276uint AccountsService::failedFingerprintLogins() const
277{
278 return getProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
279}
280
281void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
282{
283 setProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, failedFingerprintLogins);
284}
285
286uint AccountsService::failedLogins() const
287{
288 return getProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_LOGINS).toUInt();
289}
290
291void AccountsService::setFailedLogins(uint failedLogins)
292{
293 setProperty(IFACE_LOMIRI_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
294}
295
296// ====================================================
297// Everything below this line is generic helper methods
298// ====================================================
299
300void 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
306QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
307{
308 return m_properties[interface][property].value;
309}
310
311void 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
320void 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
341void 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
362void 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
390void 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
399void 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
405void 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
421void 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
436void 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}