/*
* Copyright (C) 2013-2016 Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see .
*/
#include "AccountsService.h"
#include "AccountsServiceDBusAdaptor.h"
#include
#include
#include
#include
#include
#define IFACE_ACCOUNTS_USER QStringLiteral("org.freedesktop.Accounts.User")
#define IFACE_LOCATION_HERE QStringLiteral("com.ubuntu.location.providers.here.AccountsService")
#define IFACE_UBUNTU_INPUT QStringLiteral("com.ubuntu.AccountsService.Input")
#define IFACE_UBUNTU_SECURITY QStringLiteral("com.ubuntu.AccountsService.SecurityPrivacy")
#define IFACE_UBUNTU_SECURITY_OLD QStringLiteral("com.ubuntu.touch.AccountsService.SecurityPrivacy")
#define IFACE_UNITY QStringLiteral("com.canonical.unity.AccountsService")
#define IFACE_UNITY_PRIVATE QStringLiteral("com.canonical.unity.AccountsService.Private")
#define PROP_BACKGROUND_FILE QStringLiteral("BackgroundFile")
#define PROP_DEMO_EDGES QStringLiteral("demo-edges")
#define PROP_DEMO_EDGES_COMPLETED QStringLiteral("DemoEdgesCompleted")
#define PROP_EMAIL QStringLiteral("Email")
#define PROP_ENABLE_FINGERPRINT_IDENTIFICATION QStringLiteral("EnableFingerprintIdentification")
#define PROP_ENABLE_INDICATORS_WHILE_LOCKED QStringLiteral("EnableIndicatorsWhileLocked")
#define PROP_ENABLE_LAUNCHER_WHILE_LOCKED QStringLiteral("EnableLauncherWhileLocked")
#define PROP_FAILED_FINGERPRINT_LOGINS QStringLiteral("FailedFingerprintLogins")
#define PROP_FAILED_LOGINS QStringLiteral("FailedLogins")
#define PROP_INPUT_SOURCES QStringLiteral("InputSources")
#define PROP_LICENSE_ACCEPTED QStringLiteral("LicenseAccepted")
#define PROP_LICENSE_BASE_PATH QStringLiteral("LicenseBasePath")
#define PROP_MOUSE_CURSOR_SPEED QStringLiteral("MouseCursorSpeed")
#define PROP_MOUSE_DOUBLE_CLICK_SPEED QStringLiteral("MouseDoubleClickSpeed")
#define PROP_MOUSE_PRIMARY_BUTTON QStringLiteral("MousePrimaryButton")
#define PROP_MOUSE_SCROLL_SPEED QStringLiteral("MouseScrollSpeed")
#define PROP_PASSWORD_DISPLAY_HINT QStringLiteral("PasswordDisplayHint")
#define PROP_REAL_NAME QStringLiteral("RealName")
#define PROP_STATS_WELCOME_SCREEN QStringLiteral("StatsWelcomeScreen")
#define PROP_TOUCHPAD_CURSOR_SPEED QStringLiteral("TouchpadCursorSpeed")
#define PROP_TOUCHPAD_DISABLE_WHILE_TYPING QStringLiteral("TouchpadDisableWhileTyping")
#define PROP_TOUCHPAD_DISABLE_WITH_MOUSE QStringLiteral("TouchpadDisableWithMouse")
#define PROP_TOUCHPAD_DOUBLE_CLICK_SPEED QStringLiteral("TouchpadDoubleClickSpeed")
#define PROP_TOUCHPAD_PRIMARY_BUTTON QStringLiteral("TouchpadPrimaryButton")
#define PROP_TOUCHPAD_SCROLL_SPEED QStringLiteral("TouchpadScrollSpeed")
#define PROP_TOUCHPAD_TAP_TO_CLICK QStringLiteral("TouchpadTapToClick")
#define PROP_TOUCHPAD_TWO_FINGER_SCROLL QStringLiteral("TouchpadTwoFingerScroll")
using StringMap = QMap;
using StringMapList = QList;
Q_DECLARE_METATYPE(StringMapList)
QVariant primaryButtonConverter(const QVariant &value)
{
QString stringValue = value.toString();
if (stringValue == QLatin1String("left")) {
return QVariant::fromValue(0);
} else if (stringValue == QLatin1String("right")) {
return QVariant::fromValue(1); // Mir is less clear on this -- any non-zero value is the same
} else {
return QVariant::fromValue(0); // default to left
}
}
AccountsService::AccountsService(QObject* parent, const QString &user)
: QObject(parent)
, m_service(new AccountsServiceDBusAdaptor(this))
{
m_unityInput = new QDBusInterface(QStringLiteral("com.canonical.Unity.Input"),
QStringLiteral("/com/canonical/Unity/Input"),
QStringLiteral("com.canonical.Unity.Input"),
QDBusConnection::SM_BUSNAME(), this);
connect(m_service, &AccountsServiceDBusAdaptor::propertiesChanged, this, &AccountsService::onPropertiesChanged);
connect(m_service, &AccountsServiceDBusAdaptor::maybeChanged, this, &AccountsService::onMaybeChanged);
registerProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE, QStringLiteral("backgroundFileChanged"));
registerProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, QStringLiteral("emailChanged"));
registerProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, QStringLiteral("realNameChanged"));
registerProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QStringLiteral("keymapsChanged"));
registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, QStringLiteral("hereEnabledChanged"));
registerProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH, QStringLiteral("hereLicensePathChanged"));
registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION, QStringLiteral("enableFingerprintIdentificationChanged"));
registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED, QStringLiteral("enableLauncherWhileLockedChanged"));
registerProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED, QStringLiteral("enableIndicatorsWhileLockedChanged"));
registerProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT, QStringLiteral("passwordDisplayHintChanged"));
registerProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN, QStringLiteral("statsWelcomeScreenChanged"));
registerProperty(IFACE_UNITY, PROP_DEMO_EDGES, QStringLiteral("demoEdgesChanged"));
registerProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, QStringLiteral("demoEdgesCompletedChanged"));
registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, QStringLiteral("failedFingerprintLoginsChanged"));
registerProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, QStringLiteral("failedLoginsChanged"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_CURSOR_SPEED,
m_unityInput, QStringLiteral("setMouseCursorSpeed"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_DOUBLE_CLICK_SPEED,
m_unityInput, QStringLiteral("setMouseDoubleClickSpeed"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_PRIMARY_BUTTON,
m_unityInput, QStringLiteral("setMousePrimaryButton"),
primaryButtonConverter);
registerProxy(IFACE_UBUNTU_INPUT, PROP_MOUSE_SCROLL_SPEED,
m_unityInput, QStringLiteral("setMouseScrollSpeed"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_CURSOR_SPEED,
m_unityInput, QStringLiteral("setTouchpadCursorSpeed"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_SCROLL_SPEED,
m_unityInput, QStringLiteral("setTouchpadScrollSpeed"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WHILE_TYPING,
m_unityInput, QStringLiteral("setTouchpadDisableWhileTyping"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DISABLE_WITH_MOUSE,
m_unityInput, QStringLiteral("setTouchpadDisableWithMouse"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_DOUBLE_CLICK_SPEED,
m_unityInput, QStringLiteral("setTouchpadDoubleClickSpeed"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_PRIMARY_BUTTON,
m_unityInput, QStringLiteral("setTouchpadPrimaryButton"),
primaryButtonConverter);
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TAP_TO_CLICK,
m_unityInput, QStringLiteral("setTouchpadTapToClick"));
registerProxy(IFACE_UBUNTU_INPUT, PROP_TOUCHPAD_TWO_FINGER_SCROLL,
m_unityInput, QStringLiteral("setTouchpadTwoFingerScroll"));
setUser(!user.isEmpty() ? user : QString::fromUtf8(g_get_user_name()));
}
QString AccountsService::user() const
{
return m_user;
}
void AccountsService::setUser(const QString &user)
{
if (user.isEmpty() || m_user == user)
return;
bool wasEmpty = m_user.isEmpty();
m_user = user;
Q_EMIT userChanged();
// Do the first update synchronously, as a cheap way to block rendering
// until we have the right values on bootup.
refresh(!wasEmpty);
}
bool AccountsService::demoEdges() const
{
auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES);
return value.toBool();
}
void AccountsService::setDemoEdges(bool demoEdges)
{
setProperty(IFACE_UNITY, PROP_DEMO_EDGES, demoEdges);
}
QStringList AccountsService::demoEdgesCompleted() const
{
auto value = getProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED);
return value.toStringList();
}
void AccountsService::markDemoEdgeCompleted(const QString &edge)
{
auto currentList = demoEdgesCompleted();
if (!currentList.contains(edge)) {
setProperty(IFACE_UNITY, PROP_DEMO_EDGES_COMPLETED, currentList << edge);
}
}
bool AccountsService::enableFingerprintIdentification() const
{
auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_FINGERPRINT_IDENTIFICATION);
return value.toBool();
}
bool AccountsService::enableLauncherWhileLocked() const
{
auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_LAUNCHER_WHILE_LOCKED);
return value.toBool();
}
bool AccountsService::enableIndicatorsWhileLocked() const
{
auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_ENABLE_INDICATORS_WHILE_LOCKED);
return value.toBool();
}
QString AccountsService::backgroundFile() const
{
auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_BACKGROUND_FILE);
return value.toString();
}
bool AccountsService::statsWelcomeScreen() const
{
auto value = getProperty(IFACE_UBUNTU_SECURITY_OLD, PROP_STATS_WELCOME_SCREEN);
return value.toBool();
}
AccountsService::PasswordDisplayHint AccountsService::passwordDisplayHint() const
{
auto value = getProperty(IFACE_UBUNTU_SECURITY, PROP_PASSWORD_DISPLAY_HINT);
return (PasswordDisplayHint)value.toInt();
}
bool AccountsService::hereEnabled() const
{
auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED);
return value.toBool();
}
void AccountsService::setHereEnabled(bool enabled)
{
setProperty(IFACE_LOCATION_HERE, PROP_LICENSE_ACCEPTED, enabled);
}
QString AccountsService::hereLicensePath() const
{
auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
QString hereLicensePath = value.toString();
if (hereLicensePath.isEmpty() || !QFile::exists(hereLicensePath))
hereLicensePath = QStringLiteral("");
return hereLicensePath;
}
bool AccountsService::hereLicensePathValid() const
{
auto value = getProperty(IFACE_LOCATION_HERE, PROP_LICENSE_BASE_PATH);
return !value.toString().isNull();
}
QString AccountsService::realName() const
{
auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME);
return value.toString();
}
void AccountsService::setRealName(const QString &realName)
{
setProperty(IFACE_ACCOUNTS_USER, PROP_REAL_NAME, realName);
}
QString AccountsService::email() const
{
auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL);
return value.toString();
}
void AccountsService::setEmail(const QString &email)
{
setProperty(IFACE_ACCOUNTS_USER, PROP_EMAIL, email);
}
QStringList AccountsService::keymaps() const
{
auto value = getProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES);
QDBusArgument arg = value.value();
StringMapList maps = qdbus_cast(arg);
QStringList simplifiedMaps;
Q_FOREACH(const StringMap &map, maps) {
Q_FOREACH(const QString &entry, map) {
simplifiedMaps.append(entry);
}
}
if (!simplifiedMaps.isEmpty()) {
return simplifiedMaps;
}
return {QStringLiteral("us")};
}
void AccountsService::setKeymaps(const QStringList &keymaps)
{
if (keymaps.isEmpty()) {
qWarning() << "Setting empty keymaps is not supported";
return;
}
StringMapList result;
Q_FOREACH(const QString &keymap, keymaps) {
StringMap map;
map.insert(QStringLiteral("xkb"), keymap);
result.append(map);
}
setProperty(IFACE_ACCOUNTS_USER, PROP_INPUT_SOURCES, QVariant::fromValue(result));
Q_EMIT keymapsChanged();
}
uint AccountsService::failedFingerprintLogins() const
{
return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS).toUInt();
}
void AccountsService::setFailedFingerprintLogins(uint failedFingerprintLogins)
{
setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_FINGERPRINT_LOGINS, failedFingerprintLogins);
}
uint AccountsService::failedLogins() const
{
return getProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS).toUInt();
}
void AccountsService::setFailedLogins(uint failedLogins)
{
setProperty(IFACE_UNITY_PRIVATE, PROP_FAILED_LOGINS, failedLogins);
}
// ====================================================
// Everything below this line is generic helper methods
// ====================================================
void AccountsService::emitChangedForProperty(const QString &interface, const QString &property)
{
QString signalName = m_properties[interface][property].signal;
QMetaObject::invokeMethod(this, signalName.toUtf8().data());
}
QVariant AccountsService::getProperty(const QString &interface, const QString &property) const
{
return m_properties[interface][property].value;
}
void AccountsService::setProperty(const QString &interface, const QString &property, const QVariant &value)
{
if (m_properties[interface][property].value != value) {
m_properties[interface][property].value = value;
m_service->setUserPropertyAsync(m_user, interface, property, value);
emitChangedForProperty(interface, property);
}
}
void AccountsService::updateCache(const QString &interface, const QString &property, const QVariant &value)
{
PropertyInfo &info = m_properties[interface][property];
if (info.proxyInterface) {
QVariant finalValue;
if (info.proxyConverter) {
finalValue = info.proxyConverter(value);
} else {
finalValue = value;
}
info.proxyInterface->asyncCall(info.proxyMethod, finalValue);
return; // don't bother saving a copy
}
if (info.value != value) {
info.value = value;
emitChangedForProperty(interface, property);
}
}
void AccountsService::updateProperty(const QString &interface, const QString &property)
{
QDBusPendingCall pendingReply = m_service->getUserPropertyAsync(m_user,
interface,
property);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, [this, interface, property](QDBusPendingCallWatcher* watcher) {
QDBusPendingReply reply = *watcher;
watcher->deleteLater();
if (reply.isError()) {
qWarning() << "Failed to get '" << property << "' property:" << reply.error().message();
return;
}
updateCache(interface, property, reply.value());
});
}
void AccountsService::updateAllProperties(const QString &interface, bool async)
{
QDBusPendingCall pendingReply = m_service->getAllPropertiesAsync(m_user,
interface);
QDBusPendingCallWatcher *watcher = new QDBusPendingCallWatcher(pendingReply, this);
connect(watcher, &QDBusPendingCallWatcher::finished,
this, [this, interface](QDBusPendingCallWatcher* watcher) {
QDBusPendingReply< QHash > reply = *watcher;
watcher->deleteLater();
if (reply.isError()) {
qWarning() << "Failed to get all properties for" << interface << ":" << reply.error().message();
return;
}
auto valueHash = reply.value();
auto i = valueHash.constBegin();
while (i != valueHash.constEnd()) {
updateCache(interface, i.key(), i.value());
++i;
}
});
if (!async) {
watcher->waitForFinished();
}
}
void AccountsService::registerProxy(const QString &interface, const QString &property, QDBusInterface *iface, const QString &method, ProxyConverter converter)
{
registerProperty(interface, property, nullptr);
m_properties[interface][property].proxyInterface = iface;
m_properties[interface][property].proxyMethod = method;
m_properties[interface][property].proxyConverter = converter;
}
void AccountsService::registerProperty(const QString &interface, const QString &property, const QString &signal)
{
m_properties[interface][property] = PropertyInfo();
m_properties[interface][property].signal = signal;
}
void AccountsService::onPropertiesChanged(const QString &user, const QString &interface, const QStringList &changed)
{
if (m_user != user) {
return;
}
auto propHash = m_properties.value(interface);
auto i = propHash.constBegin();
while (i != propHash.constEnd()) {
if (changed.contains(i.key())) {
updateProperty(interface, i.key());
}
++i;
}
}
void AccountsService::onMaybeChanged(const QString &user)
{
if (m_user != user) {
return;
}
// Any of the standard properties might have changed!
auto propHash = m_properties.value(IFACE_ACCOUNTS_USER);
auto i = propHash.constBegin();
while (i != propHash.constEnd()) {
updateProperty(IFACE_ACCOUNTS_USER, i.key());
++i;
}
}
void AccountsService::refresh(bool async)
{
auto i = m_properties.constBegin();
while (i != m_properties.constEnd()) {
updateAllProperties(i.key(), async);
++i;
}
}