diff --git a/src/userinfo.cpp b/src/userinfo.cpp index 7502488..994be6c 100644 --- a/src/userinfo.cpp +++ b/src/userinfo.cpp @@ -36,6 +36,7 @@ #include #include #include +#include #include #include #include @@ -44,6 +45,7 @@ namespace { const auto UserDatabaseFile = QStringLiteral("/etc/passwd"); +const auto GroupDatabaseFile = QStringLiteral("/etc/group"); enum SpecialIds : uid_t { DeviceOwnerId = 100000, @@ -70,6 +72,7 @@ UserInfoPrivate::UserInfoPrivate() : m_uid(InvalidId) , m_loggedIn(false) , m_watcher(nullptr) + , m_alone(Unknown) { } @@ -82,6 +85,7 @@ UserInfoPrivate::UserInfoPrivate(struct passwd *pwd) // counted as they don't have seats. , m_loggedIn(sd_uid_is_on_seat(m_uid, 1, "seat0") > 0) , m_watcher(nullptr) + , m_alone(Unknown) { } @@ -120,6 +124,56 @@ void UserInfoPrivate::set(struct passwd *pwd) } } +bool UserInfoPrivate::alone() +{ + if (m_alone == Unknown) + updateAlone(true); + return m_alone == Yes; +} + +void UserInfoPrivate::updateAlone(bool force) +{ + if (!force && m_alone == Unknown) { + // Skip if the value is not needed and the check is not forced + return; + } + + Tristated alone = Yes; + + if (m_uid != InvalidId && m_uid != UnknownCurrentUserId && m_uid != DeviceOwnerId) { + // There must be at least one other user besides device owner + // if the uid is valid and known and it's not device owner + alone = No; + } else { + // Can not determine from uid, check users group + errno = 0; + struct group *grp = getgrnam("users"); + if (!grp) { + qCWarning(lcUsersLog) << "Could not read users group:" << strerror(errno); + // Guessing that user is probably alone + } else { + for (int i = 0; grp->gr_mem[i] != nullptr; ++i) { + struct passwd *pwd = getpwnam(grp->gr_mem[i]); + if (pwd && pwd->pw_uid != DeviceOwnerId) { + // Found someone that's not device owner + alone = No; + break; + } + // pwd must not be free'd + } + // grp must not be free'd + } + } + + if (m_alone != alone) { + m_alone = alone; + if (!force) { + // Emit only if something needed the value already, i.e. it was known + emit aloneChanged(); + } + } +} + /** * Construct UserInfo for the current user * @@ -326,11 +380,24 @@ bool UserInfo::updateCurrent() return false; } +/** + * Returns true if there is only one user on the device + */ +bool UserInfo::alone() +{ + Q_D(UserInfo); + return d->alone(); +} + +/** + * Resets object reloading all information + */ void UserInfo::reset() { Q_D(UserInfo); d->set((isValid()) ? getpwuid(d->m_uid) : nullptr); updateCurrent(); + d->updateAlone(); } void UserInfo::replace(QSharedPointer other) @@ -360,6 +427,10 @@ void UserInfo::replace(QSharedPointer other) if (old->m_watcher) watchForChanges(); + // If alone value was known, ensure that new d_ptr also knows it + if (old->m_alone != UserInfoPrivate::Unknown && old->alone() != d_ptr->alone()) + emit aloneChanged(); + connectSignals(); } @@ -394,29 +465,39 @@ void UserInfo::connectSignals() connect(d_ptr.data(), &UserInfoPrivate::nameChanged, this, &UserInfo::nameChanged); connect(d_ptr.data(), &UserInfoPrivate::uidChanged, this, &UserInfo::uidChanged); connect(d_ptr.data(), &UserInfoPrivate::currentChanged, this, &UserInfo::currentChanged); + connect(d_ptr.data(), &UserInfoPrivate::aloneChanged, this, &UserInfo::aloneChanged); } void UserInfo::watchForChanges() { Q_D(UserInfo); d->m_watcher = new QFileSystemWatcher(this); - if (!d->m_watcher->addPath(UserDatabaseFile)) { - qCWarning(lcUsersLog) << "Could not watch for changes in user database"; + QStringList missing = d->m_watcher->addPaths(QStringList() << UserDatabaseFile << GroupDatabaseFile); + if (missing.count() == 2) { + qCWarning(lcUsersLog) << "Could not watch for changes in user or group database"; delete d->m_watcher; d->m_watcher = nullptr; + } else if (missing.count() > 0) { + qCWarning(lcUsersLog) << "Could not watch for changes in" << missing; } else { - connect(d->m_watcher, &QFileSystemWatcher::fileChanged, this, [this] { + connect(d->m_watcher, &QFileSystemWatcher::fileChanged, this, [this] (const QString &path) { Q_D(UserInfo); - if (QFile::exists(UserDatabaseFile)) { - // Database updated, reset - qCDebug(lcUsersLog) << "Reseting model because user database changed"; - reset(); + if (QFile::exists(path)) { + if (path == UserDatabaseFile) { + // User database updated, reset model + qCDebug(lcUsersLog) << UserDatabaseFile << "changed, reseting model"; + reset(); + } else if (d->m_alone != UserInfoPrivate::Unknown) { // && path == GroupDatabaseFile + // Group database updated, update alone status + qCDebug(lcUsersLog) << GroupDatabaseFile << "changed, checking alone status again"; + d->updateAlone(); + } } - if (!d->m_watcher->files().contains(UserDatabaseFile)) { - if (QFile::exists(UserDatabaseFile) && d->m_watcher->addPath(UserDatabaseFile)) { - qCDebug(lcUsersLog) << "Re-watching user database for changes"; + if (!d->m_watcher->files().contains(path)) { + if (QFile::exists(path) && d->m_watcher->addPath(path)) { + qCDebug(lcUsersLog) << "Re-watching" << path << "for changes"; } else { - qCWarning(lcUsersLog) << "Stopped watching user database for changes"; + qCWarning(lcUsersLog) << "Stopped watching" << path << "for changes"; } } }); diff --git a/src/userinfo.h b/src/userinfo.h index 756ae2a..0f8cf3e 100644 --- a/src/userinfo.h +++ b/src/userinfo.h @@ -52,6 +52,7 @@ class SYSTEMSETTINGS_EXPORT UserInfo: public QObject Q_PROPERTY(UserType type READ type CONSTANT) Q_PROPERTY(int uid READ uid WRITE setUid NOTIFY uidChanged) Q_PROPERTY(bool current READ current NOTIFY currentChanged) + Q_PROPERTY(bool alone READ alone NOTIFY aloneChanged) friend class UserModel; @@ -77,6 +78,7 @@ class SYSTEMSETTINGS_EXPORT UserInfo: public QObject int uid() const; void setUid(int uid); bool current() const; + bool alone(); Q_INVOKABLE void reset(); @@ -90,6 +92,7 @@ class SYSTEMSETTINGS_EXPORT UserInfo: public QObject void nameChanged(); void uidChanged(); void currentChanged(); + void aloneChanged(); private: explicit UserInfo(int uid); diff --git a/src/userinfo_p.h b/src/userinfo_p.h index 5b33ce1..4f95773 100644 --- a/src/userinfo_p.h +++ b/src/userinfo_p.h @@ -49,14 +49,23 @@ class UserInfoPrivate : public QObject UserInfoPrivate(struct passwd *pwd); ~UserInfoPrivate(); + enum Tristated { + Yes = 1, + No = 0, + Unknown = -1 + }; + uid_t m_uid; QString m_username; QString m_name; bool m_loggedIn; static QWeakPointer s_current; QFileSystemWatcher *m_watcher; + Tristated m_alone; void set(struct passwd *pwd); + bool alone(); + void updateAlone(bool force = false); signals: void displayNameChanged(); @@ -64,6 +73,7 @@ class UserInfoPrivate : public QObject void nameChanged(); void uidChanged(); void currentChanged(); + void aloneChanged(); }; #endif /* USERINFOPRIVATE_H */