You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
451 lines
14 KiB
C++
451 lines
14 KiB
C++
#include "useraccount.h"
|
|
#include "useraccount_p.h"
|
|
|
|
#include <QtCore/QFile>
|
|
#include <QtDBus/QDBusMessage>
|
|
#include <QtDBus/QDBusPendingCallWatcher>
|
|
#include <QtDBus/QDBusPendingReply>
|
|
|
|
#include <cerrno>
|
|
#include <pwd.h>
|
|
#include <unistd.h>
|
|
|
|
namespace QtAccountsService {
|
|
|
|
static const QString serviceName = QStringLiteral("org.freedesktop.Accounts");
|
|
static const QString userInterface = QStringLiteral("org.freedesktop.Accounts.User");
|
|
static const QString userPathPrefix = QStringLiteral("/org/freedesktop/Accounts/User");
|
|
|
|
UserAccountPrivate::UserAccountPrivate(const QDBusConnection &connection)
|
|
: bus(connection)
|
|
{
|
|
}
|
|
|
|
UserAccount::UserAccount(const QDBusConnection &bus, QObject *parent)
|
|
: UserAccount(userPathPrefix + QString::number(getuid()), bus, parent)
|
|
{
|
|
}
|
|
|
|
UserAccount::UserAccount(const QString &path, const QDBusConnection &bus, QObject *parent)
|
|
: QObject(parent)
|
|
, d_ptr(new UserAccountPrivate(bus))
|
|
{
|
|
setObjectPath(path);
|
|
}
|
|
|
|
UserAccount::~UserAccount()
|
|
{
|
|
delete d_ptr;
|
|
}
|
|
|
|
void UserAccount::setObjectPath(const QString &path)
|
|
{
|
|
if (d_ptr->objectPath == path)
|
|
return;
|
|
|
|
delete d_ptr->service;
|
|
d_ptr->objectPath = path;
|
|
d_ptr->values.clear();
|
|
d_ptr->pendingValues.clear();
|
|
d_ptr->pendingCalls.clear();
|
|
d_ptr->values.insert(QStringLiteral("Uid"), path.mid(userPathPrefix.size()).toLongLong());
|
|
d_ptr->service = new OrgFreedesktopAccountsUserInterface(serviceName, path,
|
|
d_ptr->bus, this);
|
|
d_ptr->service->setInteractiveAuthorizationAllowed(true);
|
|
d_ptr->service->setTimeout(120000);
|
|
connect(d_ptr->service, &OrgFreedesktopAccountsUserInterface::Changed,
|
|
this, &UserAccount::refresh);
|
|
refresh();
|
|
notifyAll();
|
|
}
|
|
|
|
QVariant UserAccount::value(const char *property) const
|
|
{
|
|
return d_ptr->values.value(QString::fromLatin1(property));
|
|
}
|
|
|
|
void UserAccount::notifyAll()
|
|
{
|
|
Q_EMIT userIdChanged();
|
|
Q_EMIT groupIdChanged();
|
|
Q_EMIT accountTypeChanged();
|
|
Q_EMIT lockedChanged();
|
|
Q_EMIT automaticLoginChanged();
|
|
Q_EMIT passwordModeChanged();
|
|
Q_EMIT userNameChanged();
|
|
Q_EMIT realNameChanged();
|
|
Q_EMIT displayNameChanged();
|
|
Q_EMIT homeDirectoryChanged();
|
|
Q_EMIT shellChanged();
|
|
Q_EMIT iconFileNameChanged();
|
|
Q_EMIT emailChanged();
|
|
Q_EMIT languageChanged();
|
|
Q_EMIT locationChanged();
|
|
Q_EMIT xsessionChanged();
|
|
}
|
|
|
|
// Reads are never answered from the bus on the caller's thread: a getter
|
|
// returns the cached value and this fills the cache when the reply lands.
|
|
void UserAccount::refresh()
|
|
{
|
|
const quint64 serial = ++d_ptr->refreshSerial;
|
|
QDBusMessage message = QDBusMessage::createMethodCall(
|
|
serviceName, d_ptr->objectPath, QStringLiteral("org.freedesktop.DBus.Properties"),
|
|
QStringLiteral("GetAll"));
|
|
message << userInterface;
|
|
auto *watcher = new QDBusPendingCallWatcher(d_ptr->bus.asyncCall(message), this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this, serial](QDBusPendingCallWatcher *finished) {
|
|
QDBusPendingReply<QVariantMap> reply = *finished;
|
|
finished->deleteLater();
|
|
if (serial != d_ptr->refreshSerial)
|
|
return;
|
|
if (reply.isError()) {
|
|
qWarning("AccountsService read failed: %s",
|
|
reply.error().message().toUtf8().constData());
|
|
return;
|
|
}
|
|
applyValues(reply.value());
|
|
});
|
|
}
|
|
|
|
void UserAccount::applyValues(const QVariantMap &reply)
|
|
{
|
|
QVariantMap updated = reply;
|
|
for (auto it = d_ptr->pendingValues.cbegin(); it != d_ptr->pendingValues.cend(); ++it)
|
|
updated.insert(it.key(), it.value());
|
|
|
|
const QVariantMap previous = d_ptr->values;
|
|
d_ptr->values = updated;
|
|
|
|
struct Change {
|
|
const char *property;
|
|
void (UserAccount::*signal)();
|
|
};
|
|
static const Change changes[] = {
|
|
{"Uid", &UserAccount::userIdChanged},
|
|
{"AccountType", &UserAccount::accountTypeChanged},
|
|
{"Locked", &UserAccount::lockedChanged},
|
|
{"AutomaticLogin", &UserAccount::automaticLoginChanged},
|
|
{"PasswordMode", &UserAccount::passwordModeChanged},
|
|
{"UserName", &UserAccount::userNameChanged},
|
|
{"RealName", &UserAccount::realNameChanged},
|
|
{"HomeDirectory", &UserAccount::homeDirectoryChanged},
|
|
{"Shell", &UserAccount::shellChanged},
|
|
{"IconFile", &UserAccount::iconFileNameChanged},
|
|
{"Email", &UserAccount::emailChanged},
|
|
{"Language", &UserAccount::languageChanged},
|
|
{"Location", &UserAccount::locationChanged},
|
|
{"XSession", &UserAccount::xsessionChanged}
|
|
};
|
|
|
|
bool changed = previous != updated;
|
|
bool nameChanged = false;
|
|
for (const Change &entry : changes) {
|
|
const QString key = QString::fromLatin1(entry.property);
|
|
if (previous.value(key) == updated.value(key))
|
|
continue;
|
|
Q_EMIT (this->*entry.signal)();
|
|
if (key == QStringLiteral("Uid"))
|
|
Q_EMIT groupIdChanged();
|
|
if (key == QStringLiteral("UserName") || key == QStringLiteral("RealName"))
|
|
nameChanged = true;
|
|
}
|
|
if (nameChanged)
|
|
Q_EMIT displayNameChanged();
|
|
if (changed)
|
|
Q_EMIT accountChanged();
|
|
}
|
|
|
|
void UserAccount::watch(const QDBusPendingCall &call, const char *property,
|
|
void (UserAccount::*notify)(), bool displayName)
|
|
{
|
|
const QString key = property ? QString::fromLatin1(property) : QString();
|
|
if (property)
|
|
++d_ptr->pendingCalls[key];
|
|
auto *watcher = new QDBusPendingCallWatcher(call, this);
|
|
connect(watcher, &QDBusPendingCallWatcher::finished, this,
|
|
[this, key, notify, displayName](QDBusPendingCallWatcher *finished) {
|
|
QDBusPendingReply<> reply = *finished;
|
|
finished->deleteLater();
|
|
// Only this property's value goes: another one still in flight keeps its own.
|
|
const auto calls = d_ptr->pendingCalls.find(key);
|
|
if (calls != d_ptr->pendingCalls.end() && --*calls == 0) {
|
|
d_ptr->pendingCalls.erase(calls);
|
|
d_ptr->pendingValues.remove(key);
|
|
}
|
|
if (reply.isError()) {
|
|
qWarning("AccountsService update failed: %s",
|
|
reply.error().message().toUtf8().constData());
|
|
refresh();
|
|
return;
|
|
}
|
|
// notePendingChange() already recorded the value, so refresh() stays quiet
|
|
// unless the service settled on something else.
|
|
refresh();
|
|
if (notify)
|
|
Q_EMIT (this->*notify)();
|
|
if (displayName)
|
|
Q_EMIT displayNameChanged();
|
|
Q_EMIT accountChanged();
|
|
});
|
|
}
|
|
|
|
void UserAccount::notePendingChange(const char *property, const QVariant &value,
|
|
void (UserAccount::*notify)(), bool displayName)
|
|
{
|
|
d_ptr->pendingValues.insert(QString::fromLatin1(property), value);
|
|
d_ptr->values.insert(QString::fromLatin1(property), value);
|
|
Q_EMIT (this->*notify)();
|
|
if (displayName)
|
|
Q_EMIT displayNameChanged();
|
|
}
|
|
|
|
qlonglong UserAccount::userId() const
|
|
{
|
|
return value("Uid").toLongLong();
|
|
}
|
|
|
|
void UserAccount::setUserId(qlonglong uid)
|
|
{
|
|
if (userId() == uid)
|
|
return;
|
|
setObjectPath(userPathPrefix + QString::number(uid));
|
|
}
|
|
|
|
qlonglong UserAccount::groupId() const
|
|
{
|
|
QByteArray storage(16384, '\0');
|
|
struct passwd data;
|
|
struct passwd *result = nullptr;
|
|
for (;;) {
|
|
const int error = getpwuid_r(static_cast<uid_t>(userId()), &data,
|
|
storage.data(), storage.size(), &result);
|
|
if (error == 0)
|
|
return result ? result->pw_gid : 0;
|
|
if (error != ERANGE || storage.size() >= 1024 * 1024)
|
|
return 0;
|
|
storage.resize(storage.size() * 2);
|
|
}
|
|
}
|
|
|
|
UserAccount::AccountType UserAccount::accountType() const
|
|
{
|
|
return static_cast<AccountType>(value("AccountType").toInt());
|
|
}
|
|
|
|
void UserAccount::setAccountType(AccountType type)
|
|
{
|
|
if (accountType() == type)
|
|
return;
|
|
watch(d_ptr->service->SetAccountType(static_cast<int>(type)), "AccountType",
|
|
&UserAccount::accountTypeChanged);
|
|
notePendingChange("AccountType", static_cast<int>(type), &UserAccount::accountTypeChanged);
|
|
}
|
|
|
|
bool UserAccount::isLocked() const
|
|
{
|
|
return value("Locked").toBool();
|
|
}
|
|
|
|
void UserAccount::setLocked(bool locked)
|
|
{
|
|
if (isLocked() == locked)
|
|
return;
|
|
watch(d_ptr->service->SetLocked(locked), "Locked", &UserAccount::lockedChanged);
|
|
notePendingChange("Locked", locked, &UserAccount::lockedChanged);
|
|
}
|
|
|
|
bool UserAccount::automaticLogin() const
|
|
{
|
|
return value("AutomaticLogin").toBool();
|
|
}
|
|
|
|
void UserAccount::setAutomaticLogin(bool enabled)
|
|
{
|
|
if (automaticLogin() == enabled)
|
|
return;
|
|
watch(d_ptr->service->SetAutomaticLogin(enabled), "AutomaticLogin",
|
|
&UserAccount::automaticLoginChanged);
|
|
notePendingChange("AutomaticLogin", enabled, &UserAccount::automaticLoginChanged);
|
|
}
|
|
|
|
qlonglong UserAccount::loginFrequency() const
|
|
{
|
|
return value("LoginFrequency").toLongLong();
|
|
}
|
|
|
|
qlonglong UserAccount::loginTime() const
|
|
{
|
|
return value("LoginTime").toLongLong();
|
|
}
|
|
|
|
UserAccount::PasswordMode UserAccount::passwordMode() const
|
|
{
|
|
return static_cast<PasswordMode>(value("PasswordMode").toInt());
|
|
}
|
|
|
|
void UserAccount::setPasswordMode(PasswordMode mode)
|
|
{
|
|
if (passwordMode() == mode)
|
|
return;
|
|
watch(d_ptr->service->SetPasswordMode(static_cast<int>(mode)), "PasswordMode",
|
|
&UserAccount::passwordModeChanged);
|
|
notePendingChange("PasswordMode", static_cast<int>(mode), &UserAccount::passwordModeChanged);
|
|
}
|
|
|
|
QString UserAccount::passwordHint() const
|
|
{
|
|
return value("PasswordHint").toString();
|
|
}
|
|
|
|
bool UserAccount::isLocalAccount() const
|
|
{
|
|
return value("LocalAccount").toBool();
|
|
}
|
|
|
|
bool UserAccount::isSystemAccount() const
|
|
{
|
|
return value("SystemAccount").toBool();
|
|
}
|
|
|
|
QString UserAccount::userName() const
|
|
{
|
|
return value("UserName").toString();
|
|
}
|
|
|
|
void UserAccount::setUserName(const QString &name)
|
|
{
|
|
if (userName() == name)
|
|
return;
|
|
watch(d_ptr->service->SetUserName(name), "UserName", &UserAccount::userNameChanged, true);
|
|
notePendingChange("UserName", name, &UserAccount::userNameChanged, true);
|
|
}
|
|
|
|
QString UserAccount::realName() const
|
|
{
|
|
return value("RealName").toString();
|
|
}
|
|
|
|
void UserAccount::setRealName(const QString &name)
|
|
{
|
|
if (realName() == name)
|
|
return;
|
|
watch(d_ptr->service->SetRealName(name), "RealName", &UserAccount::realNameChanged, true);
|
|
notePendingChange("RealName", name, &UserAccount::realNameChanged, true);
|
|
}
|
|
|
|
QString UserAccount::displayName() const
|
|
{
|
|
const QString real = realName();
|
|
return real.isEmpty() ? userName() : real;
|
|
}
|
|
|
|
QString UserAccount::homeDirectory() const
|
|
{
|
|
return value("HomeDirectory").toString();
|
|
}
|
|
|
|
void UserAccount::setHomeDirectory(const QString &path)
|
|
{
|
|
if (homeDirectory() == path)
|
|
return;
|
|
watch(d_ptr->service->SetHomeDirectory(path), "HomeDirectory",
|
|
&UserAccount::homeDirectoryChanged);
|
|
notePendingChange("HomeDirectory", path, &UserAccount::homeDirectoryChanged);
|
|
}
|
|
|
|
QString UserAccount::shell() const
|
|
{
|
|
return value("Shell").toString();
|
|
}
|
|
|
|
void UserAccount::setShell(const QString &path)
|
|
{
|
|
if (shell() == path)
|
|
return;
|
|
watch(d_ptr->service->SetShell(path), "Shell", &UserAccount::shellChanged);
|
|
notePendingChange("Shell", path, &UserAccount::shellChanged);
|
|
}
|
|
|
|
QString UserAccount::iconFileName() const
|
|
{
|
|
const QString path = value("IconFile").toString();
|
|
return QFile::exists(path) ? path : QString();
|
|
}
|
|
|
|
void UserAccount::setIconFileName(const QString &path)
|
|
{
|
|
if (iconFileName() == path)
|
|
return;
|
|
watch(d_ptr->service->SetIconFile(path), "IconFile", &UserAccount::iconFileNameChanged);
|
|
notePendingChange("IconFile", path, &UserAccount::iconFileNameChanged);
|
|
}
|
|
|
|
QString UserAccount::email() const
|
|
{
|
|
return value("Email").toString();
|
|
}
|
|
|
|
void UserAccount::setEmail(const QString &address)
|
|
{
|
|
if (email() == address)
|
|
return;
|
|
watch(d_ptr->service->SetEmail(address), "Email", &UserAccount::emailChanged);
|
|
notePendingChange("Email", address, &UserAccount::emailChanged);
|
|
}
|
|
|
|
QString UserAccount::language() const
|
|
{
|
|
return value("Language").toString();
|
|
}
|
|
|
|
void UserAccount::setLanguage(const QString &locale)
|
|
{
|
|
if (language() == locale)
|
|
return;
|
|
watch(d_ptr->service->SetLanguage(locale), "Language", &UserAccount::languageChanged);
|
|
notePendingChange("Language", locale, &UserAccount::languageChanged);
|
|
}
|
|
|
|
QString UserAccount::location() const
|
|
{
|
|
return value("Location").toString();
|
|
}
|
|
|
|
void UserAccount::setLocation(const QString &place)
|
|
{
|
|
if (location() == place)
|
|
return;
|
|
watch(d_ptr->service->SetLocation(place), "Location", &UserAccount::locationChanged);
|
|
notePendingChange("Location", place, &UserAccount::locationChanged);
|
|
}
|
|
|
|
QString UserAccount::xsession() const
|
|
{
|
|
return value("XSession").toString();
|
|
}
|
|
|
|
void UserAccount::setXSession(const QString &session)
|
|
{
|
|
if (xsession() == session)
|
|
return;
|
|
watch(d_ptr->service->SetXSession(session), "XSession", &UserAccount::xsessionChanged);
|
|
notePendingChange("XSession", session, &UserAccount::xsessionChanged);
|
|
}
|
|
|
|
void UserAccount::setPassword(const QString &password, const QString &hint)
|
|
{
|
|
watch(d_ptr->service->SetPassword(password, hint));
|
|
}
|
|
|
|
void UserAccount::setPasswordHint(const QString &hint)
|
|
{
|
|
if (passwordHint() != hint)
|
|
watch(d_ptr->service->SetPasswordHint(hint));
|
|
}
|
|
|
|
}
|
|
|
|
#include "moc_useraccount.cpp"
|