diff --git a/accounts/accountsmanager.cpp b/accounts/accountsmanager.cpp index b627fc2..d450b2f 100644 --- a/accounts/accountsmanager.cpp +++ b/accounts/accountsmanager.cpp @@ -33,18 +33,13 @@ UserAccount *AccountsManagerPrivate::account(const QString &path, quint64 reques if (!user) { user = new UserAccount(path, service->connection(), owner); accounts.insert(path, user); - QObject::connect(user, &UserAccount::userNameChanged, owner, - [this, user]() { - const QString path = pathFor(user); - if (!path.isEmpty()) - names.insert(path, user->userName()); - }); - QObject::connect(user, &UserAccount::accountChanged, owner, - [this, user]() { - const QString path = pathFor(user); - if (!path.isEmpty()) - names.insert(path, user->userName()); - }); + const auto updateName = [this, user]() { + const QString path = pathFor(user); + if (!path.isEmpty()) + names.insert(path, user->userName()); + }; + QObject::connect(user, &UserAccount::userNameChanged, owner, updateName); + QObject::connect(user, &UserAccount::accountChanged, owner, updateName); QObject::connect(user, &UserAccount::userIdChanged, owner, [this, user]() { const QString previousPath = pathFor(user); @@ -58,8 +53,8 @@ UserAccount *AccountsManagerPrivate::account(const QString &path, quint64 reques accounts.remove(previousPath); remove(nextPath); accounts.insert(nextPath, user); - names.insert(nextPath, user->userName().isEmpty() - ? previousName : user->userName()); + const QString userName = user->userName(); + names.insert(nextPath, userName.isEmpty() ? previousName : userName); }); } const QString currentName = user->userName(); @@ -70,6 +65,17 @@ UserAccount *AccountsManagerPrivate::account(const QString &path, quint64 reques return user; } +UserAccountList AccountsManagerPrivate::accountsFor(const QList &paths, + quint64 requestSerial) +{ + UserAccountList result; + for (const QDBusObjectPath &path : paths) { + if (UserAccount *user = account(path.path(), requestSerial)) + result.append(user); + } + return result; +} + QString AccountsManagerPrivate::pathFor(UserAccount *user) const { for (auto it = accounts.cbegin(); it != accounts.cend(); ++it) { @@ -219,12 +225,7 @@ void AccountsManager::listCachedUsers() return; } - UserAccountList result; - for (const QDBusObjectPath &path : reply.value()) { - if (UserAccount *user = d_ptr->account(path.path(), started)) - result.append(user); - } - Q_EMIT listCachedUsersFinished(result); + Q_EMIT listCachedUsersFinished(d_ptr->accountsFor(reply.value(), started)); }); } @@ -238,12 +239,7 @@ UserAccountList AccountsManager::listCachedUsersSync() return {}; } - UserAccountList result; - for (const QDBusObjectPath &path : reply.value()) { - if (UserAccount *user = d_ptr->account(path.path(), started)) - result.append(user); - } - return result; + return d_ptr->accountsFor(reply.value(), started); } UserAccount *AccountsManager::cachedUser(const QString &userName) const @@ -313,11 +309,6 @@ bool AccountsManager::deleteUser(qlonglong uid, bool removeFiles) return true; } -bool AccountsManager::deleteUser(UserAccount *account, bool removeFiles) -{ - return account && deleteUser(account->userId(), removeFiles); -} - } #include "moc_accountsmanager.cpp" diff --git a/accounts/accountsmanager.h b/accounts/accountsmanager.h index 08c527f..577ee2e 100644 --- a/accounts/accountsmanager.h +++ b/accounts/accountsmanager.h @@ -30,7 +30,6 @@ public: Q_INVOKABLE void createUser(const QString &userName, const QString &fullName, UserAccount::AccountType accountType); Q_INVOKABLE bool deleteUser(qlonglong uid, bool removeFiles); - bool deleteUser(UserAccount *account, bool removeFiles); Q_SIGNALS: void userCreated(QtAccountsService::UserAccount *account); diff --git a/accounts/accountsmanager_p.h b/accounts/accountsmanager_p.h index 6056964..27d2cb1 100644 --- a/accounts/accountsmanager_p.h +++ b/accounts/accountsmanager_p.h @@ -16,6 +16,7 @@ public: UserAccount *account(const QString &path, quint64 requestSerial, const QString &name = QString()); + UserAccountList accountsFor(const QList &paths, quint64 requestSerial); QString pathFor(UserAccount *user) const; void remove(const QString &path); void removedByService(const QString &path); diff --git a/accounts/tests/accountsmanager_test.cpp b/accounts/tests/accountsmanager_test.cpp index d30f51f..378af32 100644 --- a/accounts/tests/accountsmanager_test.cpp +++ b/accounts/tests/accountsmanager_test.cpp @@ -13,6 +13,11 @@ using namespace QtAccountsService; static const QString serviceName = QStringLiteral("org.freedesktop.Accounts"); static const QString managerPath = QStringLiteral("/org/freedesktop/Accounts"); +static const QStringList userNames = { + QStringLiteral("alice"), QStringLiteral("bob"), QStringLiteral("carol"), + QStringLiteral("error"), QStringLiteral("delete-before-cache"), + QStringLiteral("delete-before-uncache"), QStringLiteral("created") +}; class TestUserAdaptor : public QDBusAbstractAdaptor { @@ -134,13 +139,8 @@ Q_SIGNALS: private: static QDBusObjectPath userPath(const QString &name) { - static const QStringList names = { - QStringLiteral("alice"), QStringLiteral("bob"), QStringLiteral("carol"), - QStringLiteral("error"), QStringLiteral("delete-before-cache"), - QStringLiteral("delete-before-uncache"), QStringLiteral("created") - }; return QDBusObjectPath(managerPath + QStringLiteral("/User") - + QString::number(1001 + names.indexOf(name))); + + QString::number(1001 + userNames.indexOf(name))); } }; @@ -417,19 +417,12 @@ int main(int argc, char **argv) || !bus.registerObject(managerPath, &manager, QDBusConnection::ExportAdaptors)) return 1; - static const QStringList names = { - QStringLiteral("alice"), QStringLiteral("bob"), QStringLiteral("carol"), - QStringLiteral("error"), QStringLiteral("delete-before-cache"), - QStringLiteral("delete-before-uncache"), QStringLiteral("created") - }; - QList users; - for (int i = 0; i < names.size(); ++i) { + for (int i = 0; i < userNames.size(); ++i) { QObject *user = new QObject(&manager); - new TestUserAdaptor(user, names.at(i), 1001 + i); + new TestUserAdaptor(user, userNames.at(i), 1001 + i); const QString path = managerPath + QStringLiteral("/User") + QString::number(1001 + i); if (!bus.registerObject(path, user, QDBusConnection::ExportAdaptors)) return 1; - users.append(user); } return app.exec(); } diff --git a/accounts/useraccount.cpp b/accounts/useraccount.cpp index 816b53e..c43dc25 100644 --- a/accounts/useraccount.cpp +++ b/accounts/useraccount.cpp @@ -51,16 +51,12 @@ void UserAccount::setObjectPath(const QString &path) d_ptr->objectPath = path; d_ptr->values.clear(); d_ptr->values.insert(QStringLiteral("Uid"), path.mid(userPathPrefix.size()).toLongLong()); - d_ptr->values.insert(QStringLiteral("AccountType"), 0); - d_ptr->values.insert(QStringLiteral("Locked"), false); - d_ptr->values.insert(QStringLiteral("AutomaticLogin"), false); - d_ptr->values.insert(QStringLiteral("PasswordMode"), static_cast(NonePasswordMode)); 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::handleAccountChanged); + this, &UserAccount::refresh); notifyAll(); } @@ -84,11 +80,6 @@ void UserAccount::notifyAll() Q_EMIT xsessionChanged(); } -void UserAccount::handleAccountChanged() -{ - refresh(); -} - void UserAccount::refresh() { QDBusInterface properties(serviceName, d_ptr->objectPath, @@ -147,7 +138,7 @@ void UserAccount::refresh() }; bool changed = false; - bool displayNameChanged = false; + bool nameChanged = false; for (const Change &entry : changes) { const QString key = QString::fromLatin1(entry.property); if (previous.value(key) == updated.value(key)) @@ -157,19 +148,20 @@ void UserAccount::refresh() if (key == QStringLiteral("Uid")) Q_EMIT groupIdChanged(); if (key == QStringLiteral("UserName") || key == QStringLiteral("RealName")) - displayNameChanged = true; + nameChanged = true; } - if (displayNameChanged) - Q_EMIT this->displayNameChanged(); + if (nameChanged) + Q_EMIT displayNameChanged(); if (changed) Q_EMIT accountChanged(); } -void UserAccount::watch(const QDBusPendingCall &call) +void UserAccount::watch(const QDBusPendingCall &call, void (UserAccount::*notify)(), + bool displayName) { auto *watcher = new QDBusPendingCallWatcher(call, this); connect(watcher, &QDBusPendingCallWatcher::finished, this, - [this](QDBusPendingCallWatcher *finished) { + [this, notify, displayName](QDBusPendingCallWatcher *finished) { QDBusPendingReply<> reply = *finished; finished->deleteLater(); if (reply.isError()) { @@ -179,6 +171,12 @@ void UserAccount::watch(const QDBusPendingCall &call) return; } refresh(); + // The getters read the service, which holds the requested value only once Set has + // replied; refresh() stays quiet because notePendingChange() already recorded it. + if (notify) + Q_EMIT (this->*notify)(); + if (displayName) + Q_EMIT displayNameChanged(); Q_EMIT accountChanged(); }); } @@ -229,7 +227,7 @@ void UserAccount::setAccountType(AccountType type) { if (accountType() == type) return; - watch(d_ptr->service->SetAccountType(static_cast(type))); + watch(d_ptr->service->SetAccountType(static_cast(type)), &UserAccount::accountTypeChanged); notePendingChange("AccountType", static_cast(type), &UserAccount::accountTypeChanged); } @@ -242,7 +240,7 @@ void UserAccount::setLocked(bool locked) { if (isLocked() == locked) return; - watch(d_ptr->service->SetLocked(locked)); + watch(d_ptr->service->SetLocked(locked), &UserAccount::lockedChanged); notePendingChange("Locked", locked, &UserAccount::lockedChanged); } @@ -255,7 +253,7 @@ void UserAccount::setAutomaticLogin(bool enabled) { if (automaticLogin() == enabled) return; - watch(d_ptr->service->SetAutomaticLogin(enabled)); + watch(d_ptr->service->SetAutomaticLogin(enabled), &UserAccount::automaticLoginChanged); notePendingChange("AutomaticLogin", enabled, &UserAccount::automaticLoginChanged); } @@ -278,7 +276,8 @@ void UserAccount::setPasswordMode(PasswordMode mode) { if (passwordMode() == mode) return; - watch(d_ptr->service->SetPasswordMode(static_cast(mode))); + watch(d_ptr->service->SetPasswordMode(static_cast(mode)), + &UserAccount::passwordModeChanged); notePendingChange("PasswordMode", static_cast(mode), &UserAccount::passwordModeChanged); } @@ -306,7 +305,7 @@ void UserAccount::setUserName(const QString &name) { if (userName() == name) return; - watch(d_ptr->service->SetUserName(name)); + watch(d_ptr->service->SetUserName(name), &UserAccount::userNameChanged, true); notePendingChange("UserName", name, &UserAccount::userNameChanged, true); } @@ -319,7 +318,7 @@ void UserAccount::setRealName(const QString &name) { if (realName() == name) return; - watch(d_ptr->service->SetRealName(name)); + watch(d_ptr->service->SetRealName(name), &UserAccount::realNameChanged, true); notePendingChange("RealName", name, &UserAccount::realNameChanged, true); } @@ -338,7 +337,7 @@ void UserAccount::setHomeDirectory(const QString &path) { if (homeDirectory() == path) return; - watch(d_ptr->service->SetHomeDirectory(path)); + watch(d_ptr->service->SetHomeDirectory(path), &UserAccount::homeDirectoryChanged); notePendingChange("HomeDirectory", path, &UserAccount::homeDirectoryChanged); } @@ -351,7 +350,7 @@ void UserAccount::setShell(const QString &path) { if (shell() == path) return; - watch(d_ptr->service->SetShell(path)); + watch(d_ptr->service->SetShell(path), &UserAccount::shellChanged); notePendingChange("Shell", path, &UserAccount::shellChanged); } @@ -365,7 +364,7 @@ void UserAccount::setIconFileName(const QString &path) { if (iconFileName() == path) return; - watch(d_ptr->service->SetIconFile(path)); + watch(d_ptr->service->SetIconFile(path), &UserAccount::iconFileNameChanged); notePendingChange("IconFile", path, &UserAccount::iconFileNameChanged); } @@ -378,7 +377,7 @@ void UserAccount::setEmail(const QString &address) { if (email() == address) return; - watch(d_ptr->service->SetEmail(address)); + watch(d_ptr->service->SetEmail(address), &UserAccount::emailChanged); notePendingChange("Email", address, &UserAccount::emailChanged); } @@ -391,7 +390,7 @@ void UserAccount::setLanguage(const QString &locale) { if (language() == locale) return; - watch(d_ptr->service->SetLanguage(locale)); + watch(d_ptr->service->SetLanguage(locale), &UserAccount::languageChanged); notePendingChange("Language", locale, &UserAccount::languageChanged); } @@ -404,7 +403,7 @@ void UserAccount::setLocation(const QString &place) { if (location() == place) return; - watch(d_ptr->service->SetLocation(place)); + watch(d_ptr->service->SetLocation(place), &UserAccount::locationChanged); notePendingChange("Location", place, &UserAccount::locationChanged); } @@ -417,7 +416,7 @@ void UserAccount::setXSession(const QString &session) { if (xsession() == session) return; - watch(d_ptr->service->SetXSession(session)); + watch(d_ptr->service->SetXSession(session), &UserAccount::xsessionChanged); notePendingChange("XSession", session, &UserAccount::xsessionChanged); } diff --git a/accounts/useraccount.h b/accounts/useraccount.h index 8274a44..6797383 100644 --- a/accounts/useraccount.h +++ b/accounts/useraccount.h @@ -113,14 +113,12 @@ private: void setObjectPath(const QString &path); void notifyAll(); void refresh(); - void watch(const QDBusPendingCall &call); + void watch(const QDBusPendingCall &call, void (UserAccount::*notify)() = nullptr, + bool displayName = false); void notePendingChange(const char *property, const QVariant &value, void (UserAccount::*notify)(), bool displayName = false); UserAccountPrivate *const d_ptr; - -private Q_SLOTS: - void handleAccountChanged(); }; using UserAccountList = QList; diff --git a/accounts/usersmodel.cpp b/accounts/usersmodel.cpp index 02c3539..fec949d 100644 --- a/accounts/usersmodel.cpp +++ b/accounts/usersmodel.cpp @@ -6,8 +6,7 @@ namespace QtAccountsService { UsersModelPrivate::UsersModelPrivate(UsersModel *model, const QDBusConnection &bus) - : owner(model) - , manager(new AccountsManager(bus, model)) + : manager(new AccountsManager(bus, model)) { } diff --git a/accounts/usersmodel_p.h b/accounts/usersmodel_p.h index 566b9a9..d5f75ab 100644 --- a/accounts/usersmodel_p.h +++ b/accounts/usersmodel_p.h @@ -17,9 +17,8 @@ struct UserRow class UsersModelPrivate { public: - UsersModelPrivate(UsersModel *owner, const QDBusConnection &bus); + UsersModelPrivate(UsersModel *model, const QDBusConnection &bus); - UsersModel *owner; AccountsManager *manager; QVector rows; }; diff --git a/appearance/CMakeLists.txt b/appearance/CMakeLists.txt index f1ba7ca..605a4f4 100644 --- a/appearance/CMakeLists.txt +++ b/appearance/CMakeLists.txt @@ -5,7 +5,7 @@ set(CUTEFISH_APPEARANCE_SOURCES wallpaper.h ) -find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml DBus) +find_package(Qt6 REQUIRED COMPONENTS Core Qml DBus) include(GNUInstallDirs) @@ -20,7 +20,6 @@ target_include_directories(cutefish-framework-appearance target_link_libraries(cutefish-framework-appearance PUBLIC Qt6::Core - Qt6::Gui Qt6::DBus ) diff --git a/appearance/appearance.cpp b/appearance/appearance.cpp index 2c6edfb..3c96070 100644 --- a/appearance/appearance.cpp +++ b/appearance/appearance.cpp @@ -34,27 +34,26 @@ void Appearance::init() if (!m_interface->isValid()) return; - connect(m_interface, SIGNAL(darkModeChanged(bool)), this, SLOT(onDarkModeChanged(bool))); - connect(m_interface, SIGNAL(wallpaperChanged(QString)), this, - SLOT(onWallpaperChanged(QString))); + connect(m_interface, SIGNAL(darkModeChanged(bool)), this, SIGNAL(darkModeChanged())); + connect(m_interface, SIGNAL(wallpaperChanged(QString)), this, SIGNAL(wallpaperChanged())); connect(m_interface, SIGNAL(userWallpapersChanged()), this, SIGNAL(userWallpapersChanged())); connect(m_interface, SIGNAL(wallpaperFoldersChanged()), this, SIGNAL(wallpaperFoldersChanged())); connect(m_interface, SIGNAL(wallpaperFolderImagesChanged(QString)), this, SIGNAL(wallpaperFolderImagesChanged(QString))); - connect(m_interface, SIGNAL(accentColorChanged(int)), this, SLOT(onAccentColorChanged(int))); + connect(m_interface, SIGNAL(accentColorChanged(int)), this, SIGNAL(accentColorIndexChanged())); connect(m_interface, SIGNAL(darkModeDimsWallpaerChanged()), this, - SLOT(onDarkModeDimsWallpaperChanged())); + SIGNAL(dimsWallpaperChanged())); connect(m_interface, SIGNAL(continuousCornersChanged(bool)), this, SLOT(onContinuousCornersChanged(bool))); connect(m_interface, SIGNAL(systemFontPointSizeChanged()), this, - SLOT(onFontPointSizeChanged())); - connect(m_interface, SIGNAL(systemFontChanged()), this, SLOT(onFontFamilyChanged())); + SIGNAL(fontPointSizeChanged())); + connect(m_interface, SIGNAL(systemFontChanged()), this, SIGNAL(fontFamilyChanged())); connect(m_interface, SIGNAL(iconThemeChanged()), this, SIGNAL(iconThemeChanged())); - connect(m_interface, SIGNAL(backgroundTypeChanged()), this, SLOT(onBackgroundTypeChanged())); - connect(m_interface, SIGNAL(backgroundColorChanged()), this, SLOT(onBackgroundColorChanged())); + connect(m_interface, SIGNAL(backgroundTypeChanged()), this, SIGNAL(backgroundTypeChanged())); + connect(m_interface, SIGNAL(backgroundColorChanged()), this, SIGNAL(backgroundColorChanged())); connect(m_interface, SIGNAL(backgroundVisibleChanged()), this, - SLOT(onBackgroundVisibleChanged())); + SIGNAL(backgroundVisibleChanged())); emit darkModeChanged(); emit wallpaperChanged(); @@ -77,12 +76,6 @@ QVariant Appearance::serviceProperty(const char *name) const return m_interface && m_interface->isValid() ? m_interface->property(name) : QVariant(); } -void Appearance::callService(const char *method) -{ - if (m_interface && m_interface->isValid()) - m_interface->call(method); -} - void Appearance::callService(const char *method, const QVariant &value) { if (m_interface && m_interface->isValid()) @@ -272,56 +265,8 @@ void Appearance::setFontRendering(bool antialias, const QString &hintStyle) m_interface->call("setFontRendering", antialias, hintStyle); } -void Appearance::onDarkModeChanged(bool darkMode) -{ - Q_UNUSED(darkMode); - emit darkModeChanged(); -} - -void Appearance::onWallpaperChanged(const QString &path) -{ - Q_UNUSED(path); - emit wallpaperChanged(); -} - -void Appearance::onAccentColorChanged(int accentColor) -{ - Q_UNUSED(accentColor); - emit accentColorIndexChanged(); -} - void Appearance::onContinuousCornersChanged(bool continuousCorners) { m_continuousCorners = continuousCorners; emit continuousCornersChanged(); } - -void Appearance::onDarkModeDimsWallpaperChanged() -{ - emit dimsWallpaperChanged(); -} - -void Appearance::onFontPointSizeChanged() -{ - emit fontPointSizeChanged(); -} - -void Appearance::onFontFamilyChanged() -{ - emit fontFamilyChanged(); -} - -void Appearance::onBackgroundTypeChanged() -{ - emit backgroundTypeChanged(); -} - -void Appearance::onBackgroundColorChanged() -{ - emit backgroundColorChanged(); -} - -void Appearance::onBackgroundVisibleChanged() -{ - emit backgroundVisibleChanged(); -} diff --git a/appearance/appearance.h b/appearance/appearance.h index 0730081..fbed272 100644 --- a/appearance/appearance.h +++ b/appearance/appearance.h @@ -105,20 +105,10 @@ signals: private slots: void init(); - void onDarkModeChanged(bool darkMode); - void onWallpaperChanged(const QString &path); - void onAccentColorChanged(int accentColor); void onContinuousCornersChanged(bool continuousCorners); - void onDarkModeDimsWallpaperChanged(); - void onFontPointSizeChanged(); - void onFontFamilyChanged(); - void onBackgroundTypeChanged(); - void onBackgroundColorChanged(); - void onBackgroundVisibleChanged(); private: QVariant serviceProperty(const char *name) const; - void callService(const char *method); void callService(const char *method, const QVariant &value); QDBusInterface *m_interface; diff --git a/application/applicationregistry.cpp b/application/applicationregistry.cpp index 020dc36..45eb2c4 100644 --- a/application/applicationregistry.cpp +++ b/application/applicationregistry.cpp @@ -1,6 +1,7 @@ #include "applicationregistry.h" #include +#include #include #include #include @@ -63,10 +64,9 @@ public: } } - QPointer registry = m_registry; QMetaObject::invokeMethod( - m_registry, - [registry, results]() { + QCoreApplication::instance(), + [registry = m_registry, results]() { if (registry) registry->applyScan(results); }, @@ -74,7 +74,7 @@ public: } private: - ApplicationRegistry *m_registry; + QPointer m_registry; QString m_language; }; @@ -382,16 +382,13 @@ void ApplicationRegistry::onLanguageChanged() void ApplicationRegistry::startScan() { - if (m_scanInProgress) { + if (m_loading) { m_scanQueued = true; return; } - m_scanInProgress = true; - if (!m_loading) { - m_loading = true; - emit loadingChanged(); - } + m_loading = true; + emit loadingChanged(); m_language = DesktopEntry::displayLanguage(); QThreadPool::globalInstance()->start(new DesktopEntryScanner(this, m_language)); } @@ -400,7 +397,6 @@ void ApplicationRegistry::applyScan(const QList &results) { const bool wasLoading = m_loading; m_loading = false; - m_scanInProgress = false; QHash oldEntries = m_entriesById; QHash newEntries; @@ -451,7 +447,9 @@ void ApplicationRegistry::applyScan(const QList &results) void ApplicationRegistry::monitorPaths() { - for (const QString &rootPath : desktopPaths()) { + const QStringList roots = desktopPaths(); + QStringList ancestors; + for (const QString &rootPath : roots) { QDir root(rootPath); if (!root.exists()) { QString parentPath = rootPath; @@ -461,8 +459,10 @@ void ApplicationRegistry::monitorPaths() break; parentPath = parent; } - if (QDir(parentPath).exists()) + if (QDir(parentPath).exists()) { m_watcher->addPath(parentPath); + ancestors.append(parentPath); + } continue; } @@ -477,5 +477,21 @@ void ApplicationRegistry::monitorPaths() while (files.hasNext()) m_watcher->addPath(files.next()); } + + // An ancestor stands in for a missing root only until that root exists. + const QStringList watched = m_watcher->directories(); + QStringList stale; + for (const QString &dir : watched) { + if (ancestors.contains(dir)) + continue; + const bool underRoot = std::any_of(roots.cbegin(), roots.cend(), + [&dir](const QString &root) { + return dir == root || dir.startsWith(root + QLatin1Char('/')); + }); + if (!underRoot) + stale.append(dir); + } + if (!stale.isEmpty()) + m_watcher->removePaths(stale); } diff --git a/application/applicationregistry.h b/application/applicationregistry.h index e32b1a3..60addb8 100644 --- a/application/applicationregistry.h +++ b/application/applicationregistry.h @@ -90,6 +90,5 @@ private: QList m_entries; QString m_language; bool m_loading = false; - bool m_scanInProgress = false; bool m_scanQueued = false; }; diff --git a/application/applicationruntime.cpp b/application/applicationruntime.cpp index e075214..e8feafb 100644 --- a/application/applicationruntime.cpp +++ b/application/applicationruntime.cpp @@ -37,6 +37,17 @@ QProcessEnvironment applicationEnvironment() return environment; } + +template +bool callBool(QDBusInterface *iface, const QString &method, const Args &...args) +{ + if (!iface->isValid()) + return false; + + const QDBusReply reply = iface->call(method, args...); + + return reply.isValid() && reply.value(); +} } ApplicationRuntime *ApplicationRuntime::instance() @@ -120,24 +131,12 @@ bool ApplicationRuntime::quitApplication(const QString &appId) if (appId.isEmpty()) return false; - QDBusInterface *iface = interface(); - if (!iface->isValid()) - return false; - - const QDBusReply reply = iface->call(QStringLiteral("quitApplication"), appId); - - return reply.isValid() && reply.value(); + return callBool(interface(), QStringLiteral("quitApplication"), appId); } bool ApplicationRuntime::quitAll() { - QDBusInterface *iface = interface(); - if (!iface->isValid()) - return false; - - const QDBusReply reply = iface->call(QStringLiteral("quitAll")); - - return reply.isValid() && reply.value(); + return callBool(interface(), QStringLiteral("quitAll")); } bool ApplicationRuntime::quitByPid(quint32 pid) @@ -145,24 +144,12 @@ bool ApplicationRuntime::quitByPid(quint32 pid) if (pid == 0) return false; - QDBusInterface *iface = interface(); - if (!iface->isValid()) - return false; - - const QDBusReply reply = iface->call(QStringLiteral("quitByPid"), pid); - - return reply.isValid() && reply.value(); + return callBool(interface(), QStringLiteral("quitByPid"), pid); } bool ApplicationRuntime::isRunning(const QString &appId) { - QDBusInterface *iface = interface(); - if (!iface->isValid()) - return false; - - const QDBusReply reply = iface->call(QStringLiteral("isRunning"), appId); - - return reply.isValid() && reply.value(); + return callBool(interface(), QStringLiteral("isRunning"), appId); } QStringList ApplicationRuntime::runningApplications() diff --git a/application/desktopentry.cpp b/application/desktopentry.cpp index 04856a0..860c6a2 100644 --- a/application/desktopentry.cpp +++ b/application/desktopentry.cpp @@ -4,11 +4,9 @@ #include "applicationruntime.h" #include "languageenvironment.h" -#include #include #include #include -#include #include namespace { @@ -21,15 +19,45 @@ QStringList splitList(const QString &value) return result; } -QString unescapeValue(QString value) +QString unescapeValue(const QString &value) { - value.replace(QStringLiteral("\\n"), QStringLiteral("\n")); - value.replace(QStringLiteral("\\t"), QStringLiteral("\t")); - value.replace(QStringLiteral("\\r"), QStringLiteral("\r")); - value.replace(QStringLiteral("\\s"), QStringLiteral(" ")); - value.replace(QStringLiteral("\\;"), QStringLiteral(";")); - value.replace(QStringLiteral("\\\\"), QStringLiteral("\\")); - return value; + // One pass, so an escaped backslash cannot start another escape. + QString result; + result.reserve(value.size()); + for (int i = 0; i < value.size(); ++i) { + const QChar ch = value.at(i); + if (ch != QLatin1Char('\\') || i + 1 == value.size()) { + result.append(ch); + continue; + } + + const QChar next = value.at(++i); + switch (next.unicode()) { + case 'n': + result.append(QLatin1Char('\n')); + break; + case 't': + result.append(QLatin1Char('\t')); + break; + case 'r': + result.append(QLatin1Char('\r')); + break; + case 's': + result.append(QLatin1Char(' ')); + break; + case ';': + result.append(QLatin1Char(';')); + break; + case '\\': + result.append(QLatin1Char('\\')); + break; + default: + result.append(ch); + result.append(next); + break; + } + } + return result; } bool parseBool(const QString &value) @@ -157,10 +185,9 @@ QString substituteToken(const QString &token, const DesktopEntry *entry, result.append(entry->path()); } else if (field == QLatin1Char('i')) { // %i is handled as two arguments by commandForArguments(). - } else if (field == QLatin1Char('f') || field == QLatin1Char('u')) { - if (!arguments.isEmpty()) - result.append(arguments.first()); - } else if (field == QLatin1Char('F') || field == QLatin1Char('U')) { + } else if (field == QLatin1Char('f') || field == QLatin1Char('u') + || field == QLatin1Char('F') || field == QLatin1Char('U')) { + // Inside a larger token a list code can take only one argument. if (!arguments.isEmpty()) result.append(arguments.first()); } @@ -361,7 +388,7 @@ bool DesktopEntry::parse(const QString &id, const QString &path, if (equals <= 0) continue; - QString key = line.left(equals).trimmed(); + const QString key = line.left(equals).trimmed(); const QString value = unescapeValue(line.mid(equals + 1).trimmed()); if (group.startsWith(QStringLiteral("Desktop Action "))) { actionValues[group.mid(QStringLiteral("Desktop Action ").size())][key] = value; @@ -369,7 +396,6 @@ bool DesktopEntry::parse(const QString &id, const QString &path, const int localeStart = key.indexOf(QLatin1Char('[')); if (localeStart > 0 && key.endsWith(QLatin1Char(']'))) { localized.insert(key, value); - key = key.left(localeStart); } else { values.insert(key, value); } @@ -414,8 +440,7 @@ void DesktopEntry::update(const DesktopEntryData &data) m_tryExec = data.tryExec; emit tryExecChanged(); } - const bool hasExecChanged = m_exec != data.exec; - if (hasExecChanged) { + if (m_exec != data.exec) { m_exec = data.exec; emit execChanged(); } diff --git a/application/mimeapps.cpp b/application/mimeapps.cpp index c7a4c82..b5cff0d 100644 --- a/application/mimeapps.cpp +++ b/application/mimeapps.cpp @@ -65,6 +65,15 @@ QString terminalId(const QString &line) entry.remove(0, 1); return entry.section(':', 0, 0); } + +bool writeLines(const QString &path, const QStringList &lines) +{ + QSaveFile output(path); + if (!output.open(QIODevice::WriteOnly)) + return false; + const auto data = (lines.join('\n') + '\n').toUtf8(); + return output.write(data) == data.size() && output.commit(); +} } // namespace QString MimeApps::userListPath() @@ -115,11 +124,7 @@ bool MimeApps::setDefault(const QString &path, const QString &mimeType, const QS auto removed = readEntries(lines, "Removed Associations", mimeType); if (removed.removeAll(desktopId)) writeEntry(lines, "Removed Associations", mimeType, removed); - QSaveFile output(path); - if (!output.open(QIODevice::WriteOnly)) - return false; - const auto data = (lines.join('\n') + '\n').toUtf8(); - return output.write(data) == data.size() && output.commit(); + return writeLines(path, lines); } QString MimeApps::defaultTerminal(const QString &path) @@ -156,9 +161,5 @@ bool MimeApps::setDefaultTerminal(const QString &path, const QString &desktopId) return !line.trimmed().startsWith('#') && terminalId(line) == desktopId; }); lines.prepend(desktopId); - QSaveFile output(path); - if (!output.open(QIODevice::WriteOnly)) - return false; - const auto data = (lines.join('\n') + '\n').toUtf8(); - return output.write(data) == data.size() && output.commit(); + return writeLines(path, lines); } diff --git a/application/waylandwindowhints.cpp b/application/waylandwindowhints.cpp index 7052d4f..eb9594d 100644 --- a/application/waylandwindowhints.cpp +++ b/application/waylandwindowhints.cpp @@ -9,6 +9,16 @@ namespace Cutefish { +namespace { + +void flushDisplay() +{ + auto *native = qGuiApp->nativeInterface(); + wl_display_flush(native->display()); +} + +} // namespace + WaylandWindowHints *WaylandWindowHints::instance() { static auto *manager = new WaylandWindowHints(qApp); @@ -72,8 +82,7 @@ void WaylandWindowHints::setSkipTaskbar(wl_surface *surface, bool skip) if (!m_manager || !surface) return; cutefish_window_hints_v1_set_skip_taskbar(m_manager, surface, skip); - auto *native = qGuiApp->nativeInterface(); - wl_display_flush(native->display()); + flushDisplay(); } void WaylandWindowHints::setAlwaysOnTop(wl_surface *surface, bool alwaysOnTop) @@ -81,8 +90,7 @@ void WaylandWindowHints::setAlwaysOnTop(wl_surface *surface, bool alwaysOnTop) if (!m_manager || !surface) return; cutefish_window_hints_v1_set_always_on_top(m_manager, surface, alwaysOnTop); - auto *native = qGuiApp->nativeInterface(); - wl_display_flush(native->display()); + flushDisplay(); } void WaylandWindowHints::setResizeAroundCenter(wl_surface *surface, bool center) @@ -92,8 +100,7 @@ void WaylandWindowHints::setResizeAroundCenter(wl_surface *surface, bool center) cutefish_window_hints_v1_set_resize_anchor(m_manager, surface, center ? CUTEFISH_WINDOW_HINTS_V1_RESIZE_ANCHOR_CENTER : CUTEFISH_WINDOW_HINTS_V1_RESIZE_ANCHOR_TOP_LEFT); - auto *native = qGuiApp->nativeInterface(); - wl_display_flush(native->display()); + flushDisplay(); } void WaylandWindowHints::setServerResize(wl_surface *surface, bool enabled) @@ -101,8 +108,7 @@ void WaylandWindowHints::setServerResize(wl_surface *surface, bool enabled) if (!m_manager || !surface) return; cutefish_window_hints_v1_set_server_resize(m_manager, surface, enabled); - auto *native = qGuiApp->nativeInterface(); - wl_display_flush(native->display()); + flushDisplay(); } } // namespace Cutefish diff --git a/application/windowhints.cpp b/application/windowhints.cpp index 96c37a2..b33954b 100644 --- a/application/windowhints.cpp +++ b/application/windowhints.cpp @@ -107,7 +107,10 @@ void WindowHints::watchSurface() }, Qt::DirectConnection); m_destroyedConnection = connect( native, &QNativeInterface::Private::QWaylandWindow::surfaceDestroyed, this, - [this] { m_surfaceDying = true; }, Qt::DirectConnection); + [this] { + m_surfaceDying = true; + setInteractiveResize(false); + }, Qt::DirectConnection); } bool WindowHints::eventFilter(QObject *object, QEvent *event) diff --git a/audio/CMakeLists.txt b/audio/CMakeLists.txt index ee1d51c..6d1ba6e 100644 --- a/audio/CMakeLists.txt +++ b/audio/CMakeLists.txt @@ -1,4 +1,4 @@ -find_package(Qt6 REQUIRED COMPONENTS Core Gui Qml Quick) +find_package(Qt6 REQUIRED COMPONENTS Core Qml) find_package(PkgConfig REQUIRED) pkg_check_modules(PIPEWIRE REQUIRED IMPORTED_TARGET libpipewire-0.3>=0.3.65) pkg_check_modules(SPA REQUIRED IMPORTED_TARGET libspa-0.2) @@ -32,8 +32,6 @@ target_include_directories(cutefish-framework-audio PUBLIC ${CMAKE_CURRENT_SOURC target_link_libraries(cutefish-framework-audio PUBLIC Qt6::Core Qt6::Qml - Qt6::Gui - Qt6::Quick PkgConfig::PIPEWIRE PkgConfig::SPA ) diff --git a/audio/audiomodel.cpp b/audio/audiomodel.cpp index d600341..57fcdca 100644 --- a/audio/audiomodel.cpp +++ b/audio/audiomodel.cpp @@ -40,10 +40,9 @@ int AudioModel::rowCount(const QModelIndex &parent) const QVariant AudioModel::data(const QModelIndex &index, int role) const { - if (!index.isValid() || index.model() != this || index.column() != 0 - || index.row() < 0 || index.row() >= m_objects.size()) + AudioObject *object = objectAt(index); + if (!object) return {}; - AudioObject *object = m_objects.at(index.row()); if (role == Qt::DisplayRole) return object->description(); if (role == ObjectRole) @@ -56,8 +55,8 @@ QVariant AudioModel::data(const QModelIndex &index, int role) const bool AudioModel::setData(const QModelIndex &index, const QVariant &value, int role) { - if (!index.isValid() || index.model() != this || index.column() != 0 - || index.row() < 0 || index.row() >= m_objects.size()) + AudioObject *object = objectAt(index); + if (!object) return false; const int propertyIndex = m_properties.value(role, -1); if (propertyIndex < 0) @@ -65,7 +64,7 @@ bool AudioModel::setData(const QModelIndex &index, const QVariant &value, int ro const QMetaProperty property = AudioObject::staticMetaObject.property(propertyIndex); if (!property.isWritable()) return false; - return property.write(m_objects.at(index.row()), value); + return property.write(object, value); } Qt::ItemFlags AudioModel::flags(const QModelIndex &index) const @@ -88,6 +87,14 @@ AudioObject *AudioModel::defaultNode() const return m_backend->defaultNode(m_kind); } +AudioObject *AudioModel::objectAt(const QModelIndex &index) const +{ + if (!index.isValid() || index.model() != this || index.column() != 0 + || index.row() < 0 || index.row() >= m_objects.size()) + return nullptr; + return m_objects.at(index.row()); +} + void AudioModel::addObject(AudioObject *object) { if (object->kind() != m_kind) diff --git a/audio/audiomodel.h b/audio/audiomodel.h index 2d316d4..c6ef95d 100644 --- a/audio/audiomodel.h +++ b/audio/audiomodel.h @@ -31,6 +31,7 @@ Q_SIGNALS: void defaultNodeChanged(); private: + AudioObject *objectAt(const QModelIndex &index) const; void addObject(AudioObject *object); void removeObject(AudioObject *object); diff --git a/audio/audioobject.cpp b/audio/audioobject.cpp index 1246560..b20f5d4 100644 --- a/audio/audioobject.cpp +++ b/audio/audioobject.cpp @@ -26,6 +26,17 @@ QVector arrayValues(const spa_pod *pod, quint32 type) return QVector(values, values + count); } +QVariantList intValues(const spa_pod *object, quint32 key) +{ + QVariantList values; + const spa_pod_prop *prop = spa_pod_find_prop(object, nullptr, key); + if (prop) { + for (int value : arrayValues(&prop->value, SPA_TYPE_Int)) + values.append(value); + } + return values; +} + int availability(quint32 value) { if (value == SPA_PARAM_AVAILABILITY_yes) @@ -153,11 +164,16 @@ quint32 AudioObject::cardIndex() const return ok ? id : InvalidId; } +const AudioObject *AudioObject::cardObject() const +{ + return m_backend ? m_backend->object(cardIndex()) : nullptr; +} + // The card's bus, such as "pci", "usb" or "bluetooth"; empty for virtual // nodes and for SoC codecs, which have no bus. QString AudioObject::bus() const { - const AudioObject *card = m_backend ? m_backend->object(cardIndex()) : nullptr; + const AudioObject *card = cardObject(); return card ? card->m_properties.value("device.bus").toString() : QString(); } @@ -170,7 +186,7 @@ int AudioObject::profileDevice() const QVariantList AudioObject::ports() const { - const AudioObject *card = m_backend ? m_backend->object(cardIndex()) : nullptr; + const AudioObject *card = cardObject(); if (!card || (m_kind != Sink && m_kind != Source)) return {}; QVariantList result; @@ -191,7 +207,7 @@ QVariantList AudioObject::ports() const int AudioObject::activePortIndex() const { - const AudioObject *card = m_backend ? m_backend->object(cardIndex()) : nullptr; + const AudioObject *card = cardObject(); if (!card) return -1; const int active = card->m_activeRoutes.value(profileDevice(), -1); @@ -388,22 +404,11 @@ int AudioObject::updateRoute(const spa_pod *param, bool current) SPA_POD_SIZE(&props->value))); } } else { - QVariantList devices; - QVariantList profiles; - const spa_pod_prop *prop = spa_pod_find_prop(param, nullptr, SPA_PARAM_ROUTE_devices); - if (prop) { - for (int value : arrayValues(&prop->value, SPA_TYPE_Int)) - devices.append(value); - } - prop = spa_pod_find_prop(param, nullptr, SPA_PARAM_ROUTE_profiles); - if (prop) { - for (int value : arrayValues(&prop->value, SPA_TYPE_Int)) - profiles.append(value); - } updateChoice(m_routes, {{"index", index}, {"name", QString::fromUtf8(name)}, {"description", QString::fromUtf8(description)}, {"priority", priority}, {"direction", direction}, {"availability", availability(available)}, - {"devices", devices}, {"profiles", profiles}}); + {"devices", intValues(param, SPA_PARAM_ROUTE_devices)}, + {"profiles", intValues(param, SPA_PARAM_ROUTE_profiles)}}); } Q_EMIT changed(); return current ? device : -1; diff --git a/audio/audioobject.h b/audio/audioobject.h index 00ef53a..9703a7a 100644 --- a/audio/audioobject.h +++ b/audio/audioobject.h @@ -101,6 +101,7 @@ private: // Returns the device of a current route, -1 otherwise. int updateRoute(const spa_pod *param, bool current); int profileDevice() const; + const AudioObject *cardObject() const; Backend *m_backend; quint32 m_id; diff --git a/audio/backend.cpp b/audio/backend.cpp index 43febbd..9c81dff 100644 --- a/audio/backend.cpp +++ b/audio/backend.cpp @@ -131,11 +131,13 @@ const pw_device_events Backend::deviceEvents = [] { binding->object->updateProfile(param, id == SPA_PARAM_Profile); else if (id == SPA_PARAM_EnumRoute || id == SPA_PARAM_Route) { const int device = binding->object->updateRoute(param, id == SPA_PARAM_Route); + if (device < 0) + return; // Only that device's nodes: the card's other routes may still hold // the previous volume of a node that has already changed. for (AudioObject *node : binding->backend->objects()) { - if (device >= 0 && node->kind() != AudioObject::Card - && node->cardIndex() == binding->object->index() && node->profileDevice() == device) + if (node->kind() != AudioObject::Card && node->cardIndex() == binding->object->index() + && node->profileDevice() == device) binding->backend->updateRouteVolume(node); } } @@ -226,8 +228,7 @@ void Backend::connectToDaemon() PW_KEY_APP_ID, "org.cutefish.Audio", nullptr), 0); } if (!m_core) { - disconnectFromDaemon(); - m_retry.start(); + fail(); return; } pw_core_add_listener(m_core, &m_coreListener, &coreEvents, this); @@ -266,13 +267,7 @@ void Backend::disconnectFromDaemon() const auto ids = m_bindings.keys(); for (quint32 id : ids) removeGlobal(id); - if (m_metadata) { - spa_hook_remove(&m_metadataListener); - pw_proxy_destroy(reinterpret_cast(m_metadata)); - m_metadata = nullptr; - } - m_metadataId = AudioObject::InvalidId; - m_metadataPermissions = 0; + releaseMetadata(); if (m_registry) { spa_hook_remove(&m_registryListener); pw_proxy_destroy(reinterpret_cast(m_registry)); @@ -302,6 +297,17 @@ void Backend::disconnectFromDaemon() updateDefaults(); } +void Backend::releaseMetadata() +{ + if (m_metadata) { + spa_hook_remove(&m_metadataListener); + pw_proxy_destroy(reinterpret_cast(m_metadata)); + m_metadata = nullptr; + } + m_metadataId = AudioObject::InvalidId; + m_metadataPermissions = 0; +} + void Backend::addGlobal(quint32 id, quint32 permissions, const char *type, quint32 version, const spa_dict *properties) { @@ -390,10 +396,7 @@ void Backend::addGlobal(quint32 id, quint32 permissions, const char *type, void Backend::removeGlobal(quint32 id) { if (id == m_metadataId && m_metadata) { - spa_hook_remove(&m_metadataListener); - pw_proxy_destroy(reinterpret_cast(m_metadata)); - m_metadata = nullptr; - m_metadataId = AudioObject::InvalidId; + releaseMetadata(); m_defaultSink.clear(); m_defaultSource.clear(); updateDefaults(); @@ -470,8 +473,9 @@ void Backend::setNodeParam(AudioObject *object, const spa_pod *param) if (!binding || binding->object != object || !object->m_writable || object->kind() == AudioObject::Card) return; Binding *card = m_bindings.value(object->cardIndex()); - if (card && card->object->m_routeProperties.contains(object->profileDevice()) - && card->object->m_activeRoutes.contains(object->profileDevice())) { + const int device = object->profileDevice(); + if (card && card->object->m_routeProperties.contains(device) + && card->object->m_activeRoutes.contains(device)) { if (!(card->permissions & PW_PERM_W)) { checkResult(-EACCES, "Set device volume"); return; @@ -480,8 +484,8 @@ void Backend::setNodeParam(AudioObject *object, const spa_pod *param) spa_pod_builder builder = SPA_POD_BUILDER_INIT(buffer, sizeof(buffer)); const auto *route = static_cast(spa_pod_builder_add_object(&builder, SPA_TYPE_OBJECT_ParamRoute, SPA_PARAM_Route, - SPA_PARAM_ROUTE_index, SPA_POD_Int(card->object->m_activeRoutes.value(object->profileDevice())), - SPA_PARAM_ROUTE_device, SPA_POD_Int(object->profileDevice()), + SPA_PARAM_ROUTE_index, SPA_POD_Int(card->object->m_activeRoutes.value(device)), + SPA_PARAM_ROUTE_device, SPA_POD_Int(device), SPA_PARAM_ROUTE_props, SPA_POD_Pod(param), SPA_PARAM_ROUTE_save, SPA_POD_Bool(true))); checkResult(pw_device_set_param(reinterpret_cast(card->proxy), SPA_PARAM_Route, 0, route), "Set device volume"); diff --git a/audio/backend.h b/audio/backend.h index 9370f42..4770415 100644 --- a/audio/backend.h +++ b/audio/backend.h @@ -56,6 +56,7 @@ private: void connectToDaemon(); void disconnectFromDaemon(); + void releaseMetadata(); void fail(); void iterate(); void addGlobal(quint32 id, quint32 permissions, const char *type, diff --git a/audio/tests/audio_test.cpp b/audio/tests/audio_test.cpp index 8935211..67f21d7 100644 --- a/audio/tests/audio_test.cpp +++ b/audio/tests/audio_test.cpp @@ -5,7 +5,6 @@ #include "teststream.h" #include -#include #include #include #include diff --git a/audio/volumemonitor.cpp b/audio/volumemonitor.cpp index 625ec24..ee931e0 100644 --- a/audio/volumemonitor.cpp +++ b/audio/volumemonitor.cpp @@ -16,14 +16,9 @@ const pw_stream_events VolumeMonitor::streamEvents = [] { events.state_changed = [](void *data, pw_stream_state, pw_stream_state state, const char *) { auto *self = static_cast(data); const bool available = state == PW_STREAM_STATE_STREAMING; - if (self->m_available != available) { - self->m_available = available; - Q_EMIT self->availableChanged(); - } - if (!available && self->m_volume != 0.0) { - self->m_volume = 0.0; - Q_EMIT self->volumeChanged(); - } + self->updateAvailable(available); + if (!available) + self->updateVolume(0.0); }; return events; }(); @@ -110,13 +105,25 @@ void VolumeMonitor::process() // Rise at once but fall at 30 dB/s, as a level meter does; raw per-buffer // peaks jump by tens of dB and make the meter flicker. const double fall = std::pow(10.0, -30.0 / 20.0 * frames / 48000.0); - const double volume = std::clamp(std::max(peak, m_volume * fall), 0.0, 1.0); + updateVolume(std::clamp(std::max(peak, m_volume * fall), 0.0, 1.0)); +} + +void VolumeMonitor::updateVolume(double volume) +{ if (m_volume != volume) { m_volume = volume; Q_EMIT volumeChanged(); } } +void VolumeMonitor::updateAvailable(bool available) +{ + if (m_available != available) { + m_available = available; + Q_EMIT availableChanged(); + } +} + void VolumeMonitor::stop() { if (m_stream) { @@ -124,14 +131,8 @@ void VolumeMonitor::stop() pw_stream_destroy(m_stream); m_stream = nullptr; } - if (m_available) { - m_available = false; - Q_EMIT availableChanged(); - } - if (m_volume != 0.0) { - m_volume = 0.0; - Q_EMIT volumeChanged(); - } + updateAvailable(false); + updateVolume(0.0); } } diff --git a/audio/volumemonitor.h b/audio/volumemonitor.h index 7e338a0..f4381aa 100644 --- a/audio/volumemonitor.h +++ b/audio/volumemonitor.h @@ -31,6 +31,8 @@ private: static const pw_stream_events streamEvents; void stop(); void process(); + void updateVolume(double volume); + void updateAvailable(bool available); QPointer m_target; pw_stream *m_stream = nullptr; diff --git a/bluetooth/CMakeLists.txt b/bluetooth/CMakeLists.txt index 4a4df49..b66f6d0 100644 --- a/bluetooth/CMakeLists.txt +++ b/bluetooth/CMakeLists.txt @@ -36,7 +36,6 @@ target_link_libraries(cutefish-framework-bluetooth PUBLIC ) cutefish_install_qml_module(cutefish-framework-bluetooth) -install(FILES DevicesModel.qml DESTINATION ${INSTALL_QMLDIR}/Cutefish/Bluetooth) if(BUILD_TESTING) add_subdirectory(tests) diff --git a/bluetooth/bluetoothagent.cpp b/bluetooth/bluetoothagent.cpp index 2d2b08f..f779b55 100644 --- a/bluetooth/bluetoothagent.cpp +++ b/bluetooth/bluetoothagent.cpp @@ -57,7 +57,7 @@ bool BluetoothAgent::handleMessage(const QDBusMessage &message, const QDBusConne } if (message.member() == QStringLiteral("Cancel") || message.member() == QStringLiteral("Release")) { - m_manager->finishPrompt(QStringLiteral("org.bluez.Error.Canceled")); + m_manager->finishPrompt(); if (message.member() == QStringLiteral("Release")) { m_manager->m_agentReady = false; emit m_manager->changed(); diff --git a/bluetooth/bluetoothdbustypes.cpp b/bluetooth/bluetoothdbustypes.cpp index fd76452..c25dce0 100644 --- a/bluetooth/bluetoothdbustypes.cpp +++ b/bluetooth/bluetoothdbustypes.cpp @@ -1,64 +1,6 @@ #include "bluetoothdbustypes.h" -#include #include -namespace { -QDBusArgument &operator<<(QDBusArgument &argument, const BluetoothInterfaces &interfaces) -{ - argument.beginMap(QMetaType::fromType(), QMetaType::fromType()); - for (auto it = interfaces.cbegin(); it != interfaces.cend(); ++it) { - argument.beginMapEntry(); - argument << it.key() << it.value(); - argument.endMapEntry(); - } - argument.endMap(); - return argument; -} - -const QDBusArgument &operator>>(const QDBusArgument &argument, BluetoothInterfaces &interfaces) -{ - argument.beginMap(); - while (!argument.atEnd()) { - QString name; - QVariantMap properties; - argument.beginMapEntry(); - argument >> name >> properties; - argument.endMapEntry(); - interfaces.insert(name, properties); - } - argument.endMap(); - return argument; -} - -QDBusArgument &operator<<(QDBusArgument &argument, const BluetoothObjects &objects) -{ - argument.beginMap(QMetaType::fromType(), - QMetaType::fromType()); - for (auto it = objects.cbegin(); it != objects.cend(); ++it) { - argument.beginMapEntry(); - argument << it.key() << it.value(); - argument.endMapEntry(); - } - argument.endMap(); - return argument; -} - -const QDBusArgument &operator>>(const QDBusArgument &argument, BluetoothObjects &objects) -{ - argument.beginMap(); - while (!argument.atEnd()) { - QDBusObjectPath path; - BluetoothInterfaces interfaces; - argument.beginMapEntry(); - argument >> path >> interfaces; - argument.endMapEntry(); - objects.insert(path, interfaces); - } - argument.endMap(); - return argument; -} -} // namespace - void registerBluetoothDBusTypes() { qDBusRegisterMetaType(); diff --git a/bluetooth/bluetoothdbustypes.h b/bluetooth/bluetoothdbustypes.h index b6e03f9..8037978 100644 --- a/bluetooth/bluetoothdbustypes.h +++ b/bluetooth/bluetoothdbustypes.h @@ -15,7 +15,9 @@ namespace BluetoothDBus { inline const QString adapterInterface = QStringLiteral("org.bluez.Adapter1"); inline const QString deviceInterface = QStringLiteral("org.bluez.Device1"); inline const QString propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); +inline const QString objectManagerInterface = QStringLiteral("org.freedesktop.DBus.ObjectManager"); inline const QString agentPath = QStringLiteral("/org/cutefish/BluetoothAgent"); +inline const QString agentManagerPath = QStringLiteral("/org/bluez"); inline const QString agentManagerInterface = QStringLiteral("org.bluez.AgentManager1"); } // namespace BluetoothDBus diff --git a/bluetooth/bluetoothdevicesmodel.cpp b/bluetooth/bluetoothdevicesmodel.cpp index 05202dd..c28c881 100644 --- a/bluetooth/bluetoothdevicesmodel.cpp +++ b/bluetooth/bluetoothdevicesmodel.cpp @@ -117,14 +117,14 @@ void BluetoothDevicesModel::rebuild() const int oldCount = m_devices.size(); for (int row = m_devices.size() - 1; row >= 0; --row) { if (!m_devices.at(row) || !devices.contains(m_devices.at(row))) - removeRow(row); + removeDevice(row); } for (int row = 0; row < devices.size(); ++row) { if (row < m_devices.size() && m_devices.at(row) == devices.at(row)) continue; const int from = m_devices.indexOf(devices.at(row), row); if (from < 0) { - insertRow(row, devices.at(row)); + insertDevice(row, devices.at(row)); } else { beginMoveRows({}, from, from, {}, row); m_devices.move(from, row); @@ -135,7 +135,7 @@ void BluetoothDevicesModel::rebuild() emit countChanged(); } -void BluetoothDevicesModel::insertRow(int row, BluetoothObject *device) +void BluetoothDevicesModel::insertDevice(int row, BluetoothObject *device) { beginInsertRows({}, row, row); m_devices.insert(row, device); @@ -147,7 +147,7 @@ void BluetoothDevicesModel::insertRow(int row, BluetoothObject *device) }); } -void BluetoothDevicesModel::removeRow(int row) +void BluetoothDevicesModel::removeDevice(int row) { beginRemoveRows({}, row, row); const QPointer device = m_devices.takeAt(row); diff --git a/bluetooth/bluetoothdevicesmodel.h b/bluetooth/bluetoothdevicesmodel.h index eac06a9..a91e0b6 100644 --- a/bluetooth/bluetoothdevicesmodel.h +++ b/bluetooth/bluetoothdevicesmodel.h @@ -45,8 +45,8 @@ signals: private: bool accepts(const BluetoothObject *device) const; void rebuild(); - void insertRow(int row, BluetoothObject *device); - void removeRow(int row); + void insertDevice(int row, BluetoothObject *device); + void removeDevice(int row); QPointer m_manager; QList> m_devices; Filter m_filter = AllDevices; diff --git a/bluetooth/bluetoothmanager.cpp b/bluetooth/bluetoothmanager.cpp index 7655b55..cb439e6 100644 --- a/bluetooth/bluetoothmanager.cpp +++ b/bluetooth/bluetoothmanager.cpp @@ -3,7 +3,6 @@ #include "bluetoothrfkill.h" #include -#include #include #include #include @@ -63,12 +62,12 @@ BluetoothManager::BluetoothManager(const QDBusConnection &bus, bool monitorRfkil BluetoothManager::~BluetoothManager() { - finishPrompt(QStringLiteral("org.bluez.Error.Canceled")); + finishPrompt(); for (const auto &path : std::as_const(m_pairingOperations)) m_bus.asyncCall(QDBusMessage::createMethodCall(m_owner, path, deviceInterface, QStringLiteral("CancelPairing"))); if (m_agentReady) { - auto message = QDBusMessage::createMethodCall(m_owner, QStringLiteral("/org/bluez"), + auto message = QDBusMessage::createMethodCall(m_owner, agentManagerPath, agentManagerInterface, QStringLiteral("UnregisterAgent")); message << QVariant::fromValue(QDBusObjectPath(agentPath)); @@ -126,6 +125,8 @@ void BluetoothManager::ownerChanged(const QString &, const QString &, const QStr reset(); if (!newOwner.isEmpty()) load(newOwner); + else + setReady(); } void BluetoothManager::reset() @@ -133,19 +134,17 @@ void BluetoothManager::reset() ++m_generation; m_retryTimer.stop(); if (!m_owner.isEmpty()) { - m_bus.disconnect(m_owner, QStringLiteral("/"), - QStringLiteral("org.freedesktop.DBus.ObjectManager"), + m_bus.disconnect(m_owner, QStringLiteral("/"), objectManagerInterface, QStringLiteral("InterfacesAdded"), this, SLOT(interfacesAdded(QDBusObjectPath, BluetoothInterfaces))); - m_bus.disconnect(m_owner, QStringLiteral("/"), - QStringLiteral("org.freedesktop.DBus.ObjectManager"), + m_bus.disconnect(m_owner, QStringLiteral("/"), objectManagerInterface, QStringLiteral("InterfacesRemoved"), this, SLOT(interfacesRemoved(QDBusObjectPath, QStringList))); m_bus.disconnect(m_owner, QString(), propertiesInterface, QStringLiteral("PropertiesChanged"), this, SLOT(propertiesChanged(QString, QVariantMap, QStringList, QDBusMessage))); } - finishPrompt(QStringLiteral("org.bluez.Error.Canceled")); + finishPrompt(); m_owner.clear(); m_operational = false; m_loading = false; @@ -172,15 +171,15 @@ void BluetoothManager::load(const QString &owner) return; m_owner = owner; m_loading = true; - m_bus.connect(owner, QStringLiteral("/"), QStringLiteral("org.freedesktop.DBus.ObjectManager"), + m_bus.connect(owner, QStringLiteral("/"), objectManagerInterface, QStringLiteral("InterfacesAdded"), this, SLOT(interfacesAdded(QDBusObjectPath, BluetoothInterfaces))); - m_bus.connect(owner, QStringLiteral("/"), QStringLiteral("org.freedesktop.DBus.ObjectManager"), + m_bus.connect(owner, QStringLiteral("/"), objectManagerInterface, QStringLiteral("InterfacesRemoved"), this, SLOT(interfacesRemoved(QDBusObjectPath, QStringList))); m_bus.connect(owner, QString(), propertiesInterface, QStringLiteral("PropertiesChanged"), this, SLOT(propertiesChanged(QString, QVariantMap, QStringList, QDBusMessage))); - call(QStringLiteral("/"), QStringLiteral("org.freedesktop.DBus.ObjectManager"), + call(QStringLiteral("/"), objectManagerInterface, QStringLiteral("GetManagedObjects"), {}, [this](const QDBusMessage &reply) { if (reply.type() == QDBusMessage::ErrorMessage) { reset(); @@ -244,7 +243,7 @@ void BluetoothManager::interfacesRemoved(const QDBusObjectPath &path, const QStr return; if (interfaces.contains(deviceInterface) || interfaces.contains(adapterInterface)) { if (m_pairingPath == path.path()) - finishPrompt(QStringLiteral("org.bluez.Error.Canceled")); + finishPrompt(); const bool adapter = entry->isAdapter(); m_objects.remove(path.path()); m_discoveryOwned.remove(path.path()); @@ -425,8 +424,8 @@ void BluetoothManager::call(const QString &path, const QString &interface, const }); } -void BluetoothManager::setProperty(BluetoothObject *entry, const QString &property, - const QVariant &value) +void BluetoothManager::setObjectProperty(BluetoothObject *entry, const QString &property, + const QVariant &value) { if (!entry || object(entry->path()) != entry) return; @@ -444,13 +443,14 @@ void BluetoothManager::operate(BluetoothObject *entry, const QString &method, return; } const bool cancel = method == QStringLiteral("CancelPairing"); + const bool pair = method == QStringLiteral("Pair"); if (entry->busy() && !cancel) { deviceFailure(method, entry->name(), tr("The device is busy. Try again in a moment.")); return; } // Without an agent of its own the caller pairs through BlueZ's default one, // which the shell keeps registered. - if (method == QStringLiteral("Pair") && m_agentEnabled && !m_agentReady) { + if (pair && m_agentEnabled && !m_agentReady) { deviceFailure(method, entry->name(), tr("Bluetooth isn't ready yet. Try again in a moment.")); return; @@ -462,20 +462,18 @@ void BluetoothManager::operate(BluetoothObject *entry, const QString &method, QPointer guard(entry); const QString path = entry->path(); const QString name = entry->name(); - if (method == QStringLiteral("Pair")) + if (pair) m_pairingOperations.insert(path); call( remove ? entry->adapterPath() : path, remove ? adapterInterface : deviceInterface, method, remove ? QVariantList {QVariant::fromValue(QDBusObjectPath(path))} : arguments, - [this, guard, path, name, method, cancel](const QDBusMessage &reply) { - if (method == QStringLiteral("Pair")) + [this, guard, path, name, method, cancel, pair](const QDBusMessage &reply) { + if (pair) m_pairingOperations.remove(path); if (guard && !cancel) guard->setBusy(false); - if (method == QStringLiteral("Pair") || cancel) { - if (m_pairingPath == path) - finishPrompt(QStringLiteral("org.bluez.Error.Canceled")); - } + if ((pair || cancel) && m_pairingPath == path) + finishPrompt(); if (reply.type() == QDBusMessage::ErrorMessage && !cancel) { qCWarning(lcBluetooth) << method << "failed:" << reply.errorName() << reply.errorMessage(); @@ -492,8 +490,7 @@ void BluetoothManager::operate(BluetoothObject *entry, const QString &method, if (!quiet.contains(reply.errorName()) && !alreadyConnected) deviceFailure(method, guard ? guard->name() : name, describe(reply)); } - if (!guard || reply.type() == QDBusMessage::ErrorMessage - || method != QStringLiteral("Pair")) + if (!guard || reply.type() == QDBusMessage::ErrorMessage || !pair) return; // Trust is granted only after a successful user-initiated pairing. guard->setBusy(true); @@ -508,19 +505,17 @@ void BluetoothManager::operate(BluetoothObject *entry, const QString &method, operate(guard, QStringLiteral("Connect")); }); }, - Errors::Ignore, method == QStringLiteral("Pair") ? 180000 : 60000); + Errors::Ignore, pair ? 180000 : 60000); } void BluetoothManager::setDiscoveryRequested(bool requested) { m_discoveryFailed.clear(); m_stopRetries.clear(); - if (m_discoveryRequested == requested) { - syncDiscovery(); - return; + if (m_discoveryRequested != requested) { + m_discoveryRequested = requested; + emit changed(); } - m_discoveryRequested = requested; - emit changed(); syncDiscovery(); } @@ -613,8 +608,7 @@ void BluetoothManager::setAgentEnabled(bool enabled) operate(device, QStringLiteral("CancelPairing")); } if (m_agentReady) - call(QStringLiteral("/org/bluez"), agentManagerInterface, - QStringLiteral("UnregisterAgent"), + call(agentManagerPath, agentManagerInterface, QStringLiteral("UnregisterAgent"), {QVariant::fromValue(QDBusObjectPath(agentPath))}); m_agentReady = false; m_bus.unregisterObject(agentPath); @@ -627,22 +621,20 @@ void BluetoothManager::registerAgent() if (!m_agentEnabled || !m_operational || m_agentReady || m_registeringAgent) return; m_registeringAgent = true; - call(QStringLiteral("/org/bluez"), agentManagerInterface, QStringLiteral("RegisterAgent"), + call(agentManagerPath, agentManagerInterface, QStringLiteral("RegisterAgent"), {QVariant::fromValue(QDBusObjectPath(agentPath)), QStringLiteral("KeyboardDisplay")}, [this](const QDBusMessage &reply) { m_registeringAgent = false; if (reply.type() == QDBusMessage::ErrorMessage) return; if (!m_agentEnabled) { - call(QStringLiteral("/org/bluez"), agentManagerInterface, - QStringLiteral("UnregisterAgent"), + call(agentManagerPath, agentManagerInterface, QStringLiteral("UnregisterAgent"), {QVariant::fromValue(QDBusObjectPath(agentPath))}); return; } m_agentReady = true; emit changed(); - call(QStringLiteral("/org/bluez"), agentManagerInterface, - QStringLiteral("RequestDefaultAgent"), + call(agentManagerPath, agentManagerInterface, QStringLiteral("RequestDefaultAgent"), {QVariant::fromValue(QDBusObjectPath(agentPath))}); }); } @@ -712,9 +704,7 @@ void BluetoothManager::respondPairing(bool accept, const QString &code) void BluetoothManager::finishPrompt(const QString &error) { if (m_hasRequest) - m_bus.send(m_request.createErrorReply( - error.isEmpty() ? QStringLiteral("org.bluez.Error.Canceled") : error, - tr("Pairing request dismissed."))); + m_bus.send(m_request.createErrorReply(error, tr("Pairing request dismissed."))); m_hasRequest = false; m_request = QDBusMessage(); m_promptTimer.stop(); @@ -729,7 +719,7 @@ void BluetoothManager::finishPrompt(const QString &error) void BluetoothManager::cancelPairing() { const QString path = m_pairingPath; - finishPrompt(QStringLiteral("org.bluez.Error.Canceled")); + finishPrompt(); if (auto *device = object(path)) operate(device, QStringLiteral("CancelPairing")); } @@ -794,6 +784,8 @@ QString BluetoothManager::describe(const QDBusMessage &reply) const return tr("The device has no features this computer can use."); if (reason == QLatin1String("adapter-not-powered")) return tr("Bluetooth is turned off."); + if (name == QLatin1String("Blocked") && hardwareBlocked()) + return tr("Bluetooth is blocked by a hardware switch."); if (name == QLatin1String("NotReady")) return tr("Bluetooth isn't ready yet. Try again in a moment."); if (name == QLatin1String("InProgress") || reason == QLatin1String("busy") diff --git a/bluetooth/bluetoothmanager.h b/bluetooth/bluetoothmanager.h index bcdb9c0..961a196 100644 --- a/bluetooth/bluetoothmanager.h +++ b/bluetooth/bluetoothmanager.h @@ -80,7 +80,7 @@ public: Q_INVOKABLE void cancelPairing(); Q_INVOKABLE void refresh(); BluetoothObject *object(const QString &path) const; - void setProperty(BluetoothObject *object, const QString &property, const QVariant &value); + void setObjectProperty(BluetoothObject *object, const QString &property, const QVariant &value); void operate(BluetoothObject *object, const QString &method, const QVariantList &arguments = {}); void discovery(BluetoothObject *adapter, bool start); @@ -118,7 +118,7 @@ private: void setReady(); void syncDiscovery(); void registerAgent(); - void finishPrompt(const QString &error = QString()); + void finishPrompt(const QString &error = QStringLiteral("org.bluez.Error.Canceled")); void prompt(const QDBusMessage &message); void deviceFailure(const QString &method, const QString &device, const QString &body); QString describe(const QDBusMessage &reply) const; diff --git a/bluetooth/bluetoothobject.cpp b/bluetooth/bluetoothobject.cpp index 7647ef8..1ba6ae6 100644 --- a/bluetooth/bluetoothobject.cpp +++ b/bluetooth/bluetoothobject.cpp @@ -122,7 +122,7 @@ bool BluetoothObject::hasBluezInterface() const void BluetoothObject::setValue(const QString &property, const QVariant &value) { - m_manager->setProperty(this, property, value); + m_manager->setObjectProperty(this, property, value); } void BluetoothObject::setName(const QString &name) diff --git a/capture/captureclient.cpp b/capture/captureclient.cpp index 9b7260a..3a20393 100644 --- a/capture/captureclient.cpp +++ b/capture/captureclient.cpp @@ -11,6 +11,22 @@ #include "linux-dmabuf-v1-client-protocol.h" #include "xdg-output-unstable-v1-client-protocol.h" +namespace { + +// Only a release tells the compositor; a plain destroy leaves the server +// object until the connection closes. +void releaseOutput(const CaptureClient::Output &output) +{ + if (output.xdgOutput) + zxdg_output_v1_destroy(output.xdgOutput); + if (wl_output_get_version(output.output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) + wl_output_release(output.output); + else + wl_output_destroy(output.output); +} + +} // namespace + struct CaptureClientListeners { static void global(void *data, wl_registry *registry, uint32_t name, const char *interface, uint32_t version) @@ -69,12 +85,7 @@ struct CaptureClientListeners { }); if (it == self->m_outputs.end()) return; - if ((*it)->xdgOutput) - zxdg_output_v1_destroy((*it)->xdgOutput); - if (wl_output_get_version((*it)->output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) - wl_output_release((*it)->output); - else - wl_output_destroy((*it)->output); + releaseOutput(**it); self->m_outputs.erase(it); } @@ -108,7 +119,6 @@ struct CaptureClientListeners { static void handleClosed(void *data, ext_foreign_toplevel_handle_v1 *handle) { auto *toplevel = static_cast(data); - toplevel->identifier.clear(); ext_foreign_toplevel_handle_v1_destroy(handle); toplevel->handle = nullptr; } @@ -183,16 +193,8 @@ CaptureClient::~CaptureClient() } if (m_toplevelList) ext_foreign_toplevel_list_v1_destroy(m_toplevelList); - for (auto &output : m_outputs) { - if (output->xdgOutput) - zxdg_output_v1_destroy(output->xdgOutput); - // Only a release tells the compositor; a plain destroy leaves the - // server object until the connection closes. - if (wl_output_get_version(output->output) >= WL_OUTPUT_RELEASE_SINCE_VERSION) - wl_output_release(output->output); - else - wl_output_destroy(output->output); - } + for (const auto &output : m_outputs) + releaseOutput(*output); if (m_copyManager) ext_image_copy_capture_manager_v1_destroy(m_copyManager); if (m_dmabuf) diff --git a/capture/captureclient.h b/capture/captureclient.h index 0a7d1af..092f6a3 100644 --- a/capture/captureclient.h +++ b/capture/captureclient.h @@ -1,7 +1,6 @@ #ifndef CUTEFISH_CAPTURECLIENT_H #define CUTEFISH_CAPTURECLIENT_H -#include #include #include #include diff --git a/capture/capturesession.cpp b/capture/capturesession.cpp index e0ebdff..00e2020 100644 --- a/capture/capturesession.cpp +++ b/capture/capturesession.cpp @@ -102,6 +102,8 @@ struct CaptureSessionListeners { static void sessionStopped(void *data, ext_image_copy_capture_session_v1 *) { auto *self = static_cast(data); + if (self->m_stopped) + return; self->m_stopped = true; emit self->stopped(); } @@ -154,8 +156,10 @@ struct CaptureSessionListeners { self->freeBuffer(id); qCDebug(cfCapture) << "frame failed, reason" << reason; if (reason == EXT_IMAGE_COPY_CAPTURE_FRAME_V1_FAILURE_REASON_STOPPED) { - self->m_stopped = true; - emit self->stopped(); + if (!self->m_stopped) { + self->m_stopped = true; + emit self->stopped(); + } } else { // Unknown failures are retryable, and a buffer that no longer fits // is replaced once the new constraints arrive. @@ -239,18 +243,15 @@ bool CaptureSession::capture() if (m_stopped || m_frame || m_size.isEmpty()) return false; - int id = -1; - for (const auto &[candidate, buffer] : m_buffers) { - if (!buffer->held && !buffer->retired) { - id = candidate; - break; - } - } - if (id < 0) + const auto it = std::find_if(m_buffers.begin(), m_buffers.end(), + [](const auto &entry) { + return !entry.second->held && !entry.second->retired; + }); + if (it == m_buffers.end()) return false; - Buffer *buffer = m_buffers.at(id).get(); - m_frameBuffer = id; + Buffer *buffer = it->second.get(); + m_frameBuffer = it->first; m_frameTransform = 0; m_framePresentedNs = 0; m_frame = ext_image_copy_capture_session_v1_create_frame(m_session); diff --git a/capture/capturesession.h b/capture/capturesession.h index 6aed8c4..d0dee4f 100644 --- a/capture/capturesession.h +++ b/capture/capturesession.h @@ -19,7 +19,7 @@ struct ext_image_copy_capture_session_v1; struct ext_image_copy_capture_frame_v1; struct wl_buffer; -// One ext_image_copy_capture_v1 session into a pool of shared memory buffers. +// One ext_image_copy_capture_v1 session into a pool of shared memory or DMA-BUF buffers. // A buffer handed out stays the consumer's until releaseBuffer(). class CaptureSession : public QObject { diff --git a/cmake/CutefishFrameworkConfig.cmake.in b/cmake/CutefishFrameworkConfig.cmake.in index b109590..1776fd9 100644 --- a/cmake/CutefishFrameworkConfig.cmake.in +++ b/cmake/CutefishFrameworkConfig.cmake.in @@ -3,7 +3,7 @@ include(CMakeFindDependencyMacro) find_dependency(Qt6 COMPONENTS Core Gui DBus Qml) -# Without COMPONENTS every installed library is imported. +# Without COMPONENTS only Application, Appearance and IO are imported. set(_cutefish_framework_components ${CutefishFramework_FIND_COMPONENTS}) if(NOT _cutefish_framework_components) set(_cutefish_framework_components Application Appearance IO) diff --git a/cmake/CutefishFrameworkQml.cmake b/cmake/CutefishFrameworkQml.cmake index 9803577..862460e 100644 --- a/cmake/CutefishFrameworkQml.cmake +++ b/cmake/CutefishFrameworkQml.cmake @@ -1,13 +1,5 @@ qt_policy(SET QTP0001 NEW) -if(NOT DEFINED INSTALL_QMLDIR) - find_program(QT_PATHS_EXECUTABLE NAMES qtpaths6 REQUIRED) - execute_process(COMMAND ${QT_PATHS_EXECUTABLE} -query QT_INSTALL_QML - OUTPUT_VARIABLE INSTALL_QMLDIR - OUTPUT_STRIP_TRAILING_WHITESPACE - ) -endif() - function(cutefish_install_qml_module target) qt_query_qml_module("${target}" TARGET_PATH module_path diff --git a/media/mpriscontroller.cpp b/media/mpriscontroller.cpp index ed16678..e192e93 100644 --- a/media/mpriscontroller.cpp +++ b/media/mpriscontroller.cpp @@ -63,17 +63,22 @@ MprisController::MprisController(const QString &service, const QDBusConnection & connect(m_mprisPlayerInterface, &MprisPlayerInterface::canPauseChanged, this, &MprisController::canPauseChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::canPlayChanged, this, &MprisController::canPlayChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::canSeekChanged, this, &MprisController::canSeekChanged); - connect(m_mprisPlayerInterface, &MprisPlayerInterface::canSeekChanged, this, &MprisController::canSeekChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::loopStatusChanged, this, &MprisController::loopStatusChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::maximumRateChanged, this, &MprisController::maximumRateChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::metadataChanged, this, &MprisController::metadataChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::minimumRateChanged, this, &MprisController::minimumRateChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::playbackStatusChanged, this, &MprisController::playbackStatusChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::positionChanged, this, &MprisController::onPositionChanged); + // An unchanged or failed Position reply emits no positionChanged; re-arm here. + connect(m_mprisPlayerInterface, &MprisPlayerInterface::asyncPropertyFinished, this, [this](const QString &propertyName) { + if (propertyName == QLatin1String("Position")) { + m_requestedPosition = false; + } + }); connect(m_mprisPlayerInterface, &MprisPlayerInterface::rateChanged, this, &MprisController::rateChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::shuffleChanged, this, &MprisController::shuffleChanged); connect(m_mprisPlayerInterface, &MprisPlayerInterface::volumeChanged, this, &MprisController::volumeChanged); - connect(m_mprisPlayerInterface, &MprisPlayerInterface::seeked, this, &MprisController::seeked); + connect(m_mprisPlayerInterface, &MprisPlayerInterface::Seeked, this, &MprisController::seeked); m_mprisPlayerInterface->setUseCache(true); // This will initialize the properties, if needed @@ -305,7 +310,7 @@ bool MprisController::setPosition(const QString &aTrackId, qlonglong position) } QVariant length = m_mprisPlayerInterface->metadata()[Mpris::metadataToString(Mpris::Length)]; - if (!length.isValid()) { + if (length.isValid()) { qlonglong reportedLength = length.toLongLong(); if (position < 0 || position > reportedLength) { @@ -651,6 +656,8 @@ void MprisController::onAsyncGetAllPlayerPropertiesFinished() void MprisController::onCanControlChanged() { + Q_EMIT canControlChanged(); + // On first reception, we are using a "GetAll" so we can skip this if (m_canControlReceived) { // I could disconnect and re-connect the signals so I avoid diff --git a/media/mpriscontroller_p.h b/media/mpriscontroller_p.h index 926c04a..1eefcac 100644 --- a/media/mpriscontroller_p.h +++ b/media/mpriscontroller_p.h @@ -306,7 +306,7 @@ Q_SIGNALS: // SIGNALS void rateChanged(double rate); void shuffleChanged(bool shuffle); void volumeChanged(double volume); - void seeked(qlonglong Position); + void Seeked(qlonglong Position); private Q_SLOTS: void onPropertyChanged(const QString &propertyName, const QVariant &value); diff --git a/media/mprismanager.cpp b/media/mprismanager.cpp index 8923231..c7cdedd 100644 --- a/media/mprismanager.cpp +++ b/media/mprismanager.cpp @@ -416,7 +416,7 @@ void MprisManager::onServiceAppeared(const QString &service) connect(controller.data(), SIGNAL(playbackStatusChanged()), m_playbackStatusMapper, SLOT(map())); m_playbackStatusMapper->setMapping(controller.data(), controller->service()); // One mapper for every controller: connected once, not per player. - connect(m_playbackStatusMapper, SIGNAL(mapped(QString)), this, SLOT(onAvailableControllerPlaybackStatusChanged(QString)), Qt::UniqueConnection); + connect(m_playbackStatusMapper, &QSignalMapper::mappedString, this, &MprisManager::onAvailableControllerPlaybackStatusChanged, Qt::UniqueConnection); } if (m_currentController.isNull()) { @@ -434,7 +434,7 @@ void MprisManager::onServiceAppeared(const QString &service) m_availableControllers.prepend(controller); setCurrentController(controller); } else { - m_availableControllers.insert(1, controller); + m_availableControllers.insert(qMin(1, m_availableControllers.size()), controller); if (controller->playbackStatus() == Mpris::Playing) { m_otherPlayingControllers.prepend(controller); } @@ -471,7 +471,9 @@ void MprisManager::onServiceVanished(const QString &service) void MprisManager::onAvailableControllerPlaybackStatusChanged(const QString &service) { QSharedPointer controller = availableController(service); - Q_ASSERT(!controller.isNull()); + if (controller.isNull()) { + return; + } if (m_currentController == controller) { if (m_currentController->playbackStatus() == Mpris::Playing) { @@ -480,7 +482,11 @@ void MprisManager::onAvailableControllerPlaybackStatusChanged(const QString &ser if (!m_otherPlayingControllers.isEmpty()) { QSharedPointer currentController = m_otherPlayingControllers.takeFirst(); - m_availableControllers.move(m_availableControllers.indexOf(currentController), 0); + const qsizetype index = m_availableControllers.indexOf(currentController); + if (index >= 0) { + m_availableControllers.move(index, 0); + } + setCurrentController(currentController); } } else { @@ -493,7 +499,7 @@ void MprisManager::onAvailableControllerPlaybackStatusChanged(const QString &ser && m_currentController->playbackStatus() != Mpris::Playing) { setCurrentController(controller); } else { - m_availableControllers.move(m_availableControllers.indexOf(controller), 1); + m_availableControllers.move(m_availableControllers.indexOf(controller), qMin(1, m_availableControllers.size() - 1)); m_otherPlayingControllers.removeAll(controller); // Just in case, shouldn't be needed m_otherPlayingControllers.prepend(controller); } @@ -583,6 +589,7 @@ void MprisManager::setCurrentController(QSharedPointer controll connect(m_currentController.data(), &MprisController::metadataChanged, this, &MprisManager::metadataChanged); connect(m_currentController.data(), &MprisController::minimumRateChanged, this, &MprisManager::minimumRateChanged); connect(m_currentController.data(), &MprisController::playbackStatusChanged, this, &MprisManager::playbackStatusChanged); + connect(m_currentController.data(), &MprisController::positionChanged, this, &MprisManager::positionChanged); connect(m_currentController.data(), &MprisController::rateChanged, this, &MprisManager::rateChanged); connect(m_currentController.data(), &MprisController::shuffleChanged, this, &MprisManager::shuffleChanged); connect(m_currentController.data(), &MprisController::volumeChanged, this, &MprisManager::volumeChanged); @@ -594,6 +601,31 @@ void MprisManager::setCurrentController(QSharedPointer controll } Q_EMIT currentServiceChanged(); + + // The forwarded properties read the new controller; notify them all. + Q_EMIT canQuitChanged(); + Q_EMIT canRaiseChanged(); + Q_EMIT canSetFullscreenChanged(); + Q_EMIT desktopEntryChanged(); + Q_EMIT fullscreenChanged(); + Q_EMIT hasTrackListChanged(); + Q_EMIT identityChanged(); + Q_EMIT supportedUriSchemesChanged(); + Q_EMIT supportedMimeTypesChanged(); + Q_EMIT canControlChanged(); + Q_EMIT canGoNextChanged(); + Q_EMIT canGoPreviousChanged(); + Q_EMIT canPauseChanged(); + Q_EMIT canPlayChanged(); + Q_EMIT canSeekChanged(); + Q_EMIT loopStatusChanged(); + Q_EMIT maximumRateChanged(); + Q_EMIT metadataChanged(); + Q_EMIT minimumRateChanged(); + Q_EMIT playbackStatusChanged(); + Q_EMIT rateChanged(); + Q_EMIT shuffleChanged(); + Q_EMIT volumeChanged(); } bool MprisManager::checkController(const char *callerName) const diff --git a/media/mprisplayeradaptor.cpp b/media/mprisplayeradaptor.cpp index 086df52..098f440 100644 --- a/media/mprisplayeradaptor.cpp +++ b/media/mprisplayeradaptor.cpp @@ -246,10 +246,12 @@ void MprisPlayerAdaptor::OpenUri(const QString &Uri) QUrl url(Uri, QUrl::StrictMode); if (!url.isValid()) { player->sendErrorReply(QDBusError::InvalidArgs, QStringLiteral("Wanted to open an url but the url is invalid.")); + return; } if (!player->supportedUriSchemes().contains(url.scheme())) { player->sendErrorReply(QDBusError::NotSupported, QStringLiteral("Wanted to open an url but the scheme is not supported.")); + return; } QMimeDatabase db; diff --git a/media/mprisplayerinterface.cpp b/media/mprisplayerinterface.cpp index 8a1b29d..dd6d7cd 100644 --- a/media/mprisplayerinterface.cpp +++ b/media/mprisplayerinterface.cpp @@ -103,7 +103,7 @@ void MprisPlayerInterface::onPropertyChanged(const QString &propertyName, const Q_EMIT loopStatusChanged(m_loopStatus); } } else if (propertyName == QStringLiteral("MaximumRate")) { - bool maximumRate = value.toDouble(); + double maximumRate = value.toDouble(); if (m_maximumRate != maximumRate) { m_maximumRate = maximumRate; Q_EMIT maximumRateChanged(m_maximumRate); diff --git a/media/mprisrootinterface.cpp b/media/mprisrootinterface.cpp index 95eeae0..7735176 100644 --- a/media/mprisrootinterface.cpp +++ b/media/mprisrootinterface.cpp @@ -92,16 +92,16 @@ void MprisRootInterface::onPropertyChanged(const QString &propertyName, const QV Q_EMIT identityChanged(m_identity); } } else if (propertyName == QStringLiteral("SupportedMimeTypes")) { - QStringList supportedUriSchemes = value.toStringList(); - if (m_supportedUriSchemes != supportedUriSchemes) { - m_supportedUriSchemes = supportedUriSchemes; - Q_EMIT supportedMimeTypesChanged(m_supportedUriSchemes); - } - } else if (propertyName == QStringLiteral("SupportedUriSchemes")) { QStringList supportedMimeTypes = value.toStringList(); if (m_supportedMimeTypes != supportedMimeTypes) { m_supportedMimeTypes = supportedMimeTypes; - Q_EMIT supportedUriSchemesChanged(m_supportedMimeTypes); + Q_EMIT supportedMimeTypesChanged(m_supportedMimeTypes); + } + } else if (propertyName == QStringLiteral("SupportedUriSchemes")) { + QStringList supportedUriSchemes = value.toStringList(); + if (m_supportedUriSchemes != supportedUriSchemes) { + m_supportedUriSchemes = supportedUriSchemes; + Q_EMIT supportedUriSchemesChanged(m_supportedUriSchemes); } } else { qWarning() << Q_FUNC_INFO diff --git a/network/appletproxymodel.cpp b/network/appletproxymodel.cpp index 7a785d6..5c4c095 100644 --- a/network/appletproxymodel.cpp +++ b/network/appletproxymodel.cpp @@ -95,13 +95,13 @@ bool AppletProxyModel::lessThan(const QModelIndex &left, const QModelIndex &righ return QString::localeAwareCompare(value(left, "name").toString(), value(right, "name").toString()) > 0; // Sorted descending: whatever compares greater comes first. - const bool leftConnected = value(left, "connectionState").toInt() == NetworkModel::Activated; - const bool rightConnected = value(right, "connectionState").toInt() == NetworkModel::Activated; + const int leftState = value(left, "connectionState").toInt(); + const int rightState = value(right, "connectionState").toInt(); + const bool leftConnected = leftState == NetworkModel::Activated; + const bool rightConnected = rightState == NetworkModel::Activated; if (leftConnected != rightConnected) return rightConnected; - const int leftState = value(left, "connectionState").toInt(); - const int rightState = value(right, "connectionState").toInt(); if (leftState != rightState) return leftState > rightState; diff --git a/network/handler.cpp b/network/handler.cpp index 5ee852c..cb9d050 100644 --- a/network/handler.cpp +++ b/network/handler.cpp @@ -459,8 +459,6 @@ void Handler::importVpn(const QUrl &file) // NetworkManager's own readers, the ones nmcli and both big desktops use; whichever // reads the file first wins. - QStringList types; - QStringList plugins = VpnUtils::installedPlugins(); const QString suffix = QFileInfo(path).suffix().toLower(); const QString likely = suffix == QLatin1String("ovpn") || suffix == QLatin1String("conf") ? QStringLiteral("openvpn") @@ -468,14 +466,13 @@ void Handler::importVpn(const QUrl &file) : QString(); if (plugins.removeAll(likely)) plugins.prepend(likely); - types += plugins; - if (types.isEmpty()) { + if (plugins.isEmpty()) { Q_EMIT vpnAddFailed(tr("No VPN plugin can read this file. Install the one for your VPN, " "for example network-manager-openvpn for OpenVPN.")); return; } - importVpnAs(path, types, QString()); + importVpnAs(path, plugins, QString()); } void Handler::importVpnAs(const QString &file, QStringList types, const QString &error) diff --git a/network/networkclient.cpp b/network/networkclient.cpp index e28ae58..c41923f 100644 --- a/network/networkclient.cpp +++ b/network/networkclient.cpp @@ -119,17 +119,6 @@ QVariant NetworkClient::setting(const QString &connection, const QString &settin return plain(m_settings.value(connection).value(setting).value(key)); } -QString NetworkClient::connectionByUuid(const QString &uuid) const -{ - if (uuid.isEmpty()) - return {}; - for (auto it = m_settings.cbegin(); it != m_settings.cend(); ++it) { - if (it->value(QStringLiteral("connection")).value(QStringLiteral("uuid")).toString() == uuid) - return it.key(); - } - return {}; -} - QString NetworkClient::hotspotConnection() const { for (const QString &connection : connections()) { @@ -233,7 +222,6 @@ void NetworkClient::reset() const QStringList paths = m_objects.keys(); m_objects.clear(); m_settings.clear(); - m_unreadable.clear(); m_settingsRequests.clear(); m_permissions.clear(); @@ -344,7 +332,6 @@ void NetworkClient::interfacesRemoved(const QDBusObjectPath &path, const QString if (interfaces.contains(connectionInterface)) { m_settings.remove(objectPath); - m_unreadable.remove(objectPath); m_settingsRequests.remove(objectPath); } @@ -406,13 +393,10 @@ void NetworkClient::fetchSettings(const QString &path) m_settingsRequests.remove(path); // Another user's private profile: it exists, but its settings are not ours to read. - if (reply.type() == QDBusMessage::ReplyMessage) { + if (reply.type() == QDBusMessage::ReplyMessage) m_settings.insert(path, qdbus_cast(reply.arguments().value(0))); - m_unreadable.remove(path); - } else { + else m_settings.remove(path); - m_unreadable.insert(path); - } updateReady(); scheduleChanged(); }); diff --git a/network/networkclient.h b/network/networkclient.h index 09d45d1..96c138c 100644 --- a/network/networkclient.h +++ b/network/networkclient.h @@ -6,7 +6,6 @@ #include #include #include -#include #include #include @@ -50,7 +49,6 @@ public: // As NetworkManager sent them, so an Update hands back the D-Bus types it was given. NMVariantMapMap settings(const QString &connection) const; QVariant setting(const QString &connection, const QString &setting, const QString &key) const; - QString connectionByUuid(const QString &uuid) const; // The profile that shares this machine's connection over Wi-Fi. QString hotspotConnection() const; @@ -105,7 +103,6 @@ private: bool m_ready = false; QHash> m_objects; QHash m_settings; - QSet m_unreadable; // The newest GetSettings per profile; an older reply is stale. QHash m_settingsRequests; quint64 m_permissionsRequest = 0; diff --git a/network/networkdbustypes.cpp b/network/networkdbustypes.cpp index f0e797d..9c60ffd 100644 --- a/network/networkdbustypes.cpp +++ b/network/networkdbustypes.cpp @@ -6,33 +6,6 @@ namespace { -QDBusArgument &operator<<(QDBusArgument &argument, const NMObjects &objects) -{ - argument.beginMap(QMetaType::fromType(), QMetaType::fromType()); - for (auto it = objects.cbegin(); it != objects.cend(); ++it) { - argument.beginMapEntry(); - argument << it.key() << it.value(); - argument.endMapEntry(); - } - argument.endMap(); - return argument; -} - -const QDBusArgument &operator>>(const QDBusArgument &argument, NMObjects &objects) -{ - argument.beginMap(); - while (!argument.atEnd()) { - QDBusObjectPath path; - NMVariantMapMap interfaces; - argument.beginMapEntry(); - argument >> path >> interfaces; - argument.endMapEntry(); - objects.insert(path, interfaces); - } - argument.endMap(); - return argument; -} - QVariant read(const QDBusArgument &argument) { switch (argument.currentType()) { diff --git a/network/networkdbustypes.h b/network/networkdbustypes.h index 7a1a882..2670c90 100644 --- a/network/networkdbustypes.h +++ b/network/networkdbustypes.h @@ -34,7 +34,6 @@ inline const QString connectionInterface = QStringLiteral("org.freedesktop.NetworkManager.Settings.Connection"); inline const QString activeInterface = QStringLiteral("org.freedesktop.NetworkManager.Connection.Active"); inline const QString deviceInterface = QStringLiteral("org.freedesktop.NetworkManager.Device"); -inline const QString wiredInterface = QStringLiteral("org.freedesktop.NetworkManager.Device.Wired"); inline const QString wirelessInterface = QStringLiteral("org.freedesktop.NetworkManager.Device.Wireless"); inline const QString statisticsInterface = QStringLiteral("org.freedesktop.NetworkManager.Device.Statistics"); @@ -42,7 +41,6 @@ inline const QString accessPointInterface = QStringLiteral("org.freedesktop.Netw inline const QString ip4ConfigInterface = QStringLiteral("org.freedesktop.NetworkManager.IP4Config"); inline const QString ip6ConfigInterface = QStringLiteral("org.freedesktop.NetworkManager.IP6Config"); inline const QString agentManagerInterface = QStringLiteral("org.freedesktop.NetworkManager.AgentManager"); -inline const QString secretAgentInterface = QStringLiteral("org.freedesktop.NetworkManager.SecretAgent"); inline const QString propertiesInterface = QStringLiteral("org.freedesktop.DBus.Properties"); inline const QString objectManagerInterface = QStringLiteral("org.freedesktop.DBus.ObjectManager"); diff --git a/network/networkmodel.cpp b/network/networkmodel.cpp index 691c4a4..abf4203 100644 --- a/network/networkmodel.cpp +++ b/network/networkmodel.cpp @@ -93,6 +93,11 @@ QDateTime lastUsed(const NetworkClient *client, const QString &profile) return seconds ? QDateTime::fromSecsSinceEpoch(qint64(seconds)) : QDateTime(); } +QString profileOf(const NetworkClient *client, const QString &active) +{ + return NetworkClient::path(client->property(active, activeInterface, QStringLiteral("Connection"))); +} + // The profiles NetworkManager would activate on the device, and the one it has active. QStringList profilesFor(const NetworkClient *client, const QString &device, const QString &type) { @@ -100,7 +105,7 @@ QStringList profilesFor(const NetworkClient *client, const QString &device, cons QStringList candidates = client->property(device, deviceInterface, QStringLiteral("AvailableConnections")).toStringList(); const QString active = client->activeConnection(device); if (!active.isEmpty()) - candidates << NetworkClient::path(client->property(active, activeInterface, QStringLiteral("Connection"))); + candidates << profileOf(client, active); for (const QString &profile : std::as_const(candidates)) { if (profiles.contains(profile) || !client->readable(profile) || isPort(client, profile) @@ -133,8 +138,7 @@ NetworkModel::NetworkModel(QObject *parent) connect(m_client, &NetworkClient::changed, this, &NetworkModel::rebuild); connect(m_client, &NetworkClient::activeStateChanged, this, [this](const QString &active, uint state, uint reason) { - const QString profile = NetworkClient::path( - m_client->property(active, activeInterface, QStringLiteral("Connection"))); + const QString profile = profileOf(m_client, active); if (!VpnUtils::isVpn(m_client, profile)) return; if (state == ActiveActivating) { @@ -348,7 +352,7 @@ void NetworkModel::rebuild() void NetworkModel::addWired(const QString &device, QList &items) const { const QString active = m_client->activeConnection(device); - const QString activeProfile = NetworkClient::path(m_client->property(active, activeInterface, QStringLiteral("Connection"))); + const QString activeProfile = profileOf(m_client, active); for (const QString &profile : profilesFor(m_client, device, QStringLiteral("802-3-ethernet"))) { Item item; @@ -392,7 +396,7 @@ void NetworkModel::addWireless(const QString &device, QList &items) const const uint capabilities = m_client->property(device, wirelessInterface, QStringLiteral("WirelessCapabilities")).toUInt(); const QString activeAp = NetworkClient::path(m_client->property(device, wirelessInterface, QStringLiteral("ActiveAccessPoint"))); const QString active = m_client->activeConnection(device); - const QString activeProfile = NetworkClient::path(m_client->property(active, activeInterface, QStringLiteral("Connection"))); + const QString activeProfile = profileOf(m_client, active); const int activeState = active.isEmpty() ? int(Deactivated) : m_client->property(active, activeInterface, QStringLiteral("State")).toInt(); @@ -439,7 +443,7 @@ void NetworkModel::addWireless(const QString &device, QList &items) const network.security = security; } - QList unmatched; + bool activeUnlisted = false; for (const QString &profile : profilesFor(m_client, device, wirelessSetting)) { const QString mode = m_client->setting(profile, wirelessSetting, QStringLiteral("mode")).toString(); if (!mode.isEmpty() && mode != QLatin1String("infrastructure")) @@ -464,7 +468,7 @@ void NetworkModel::addWireless(const QString &device, QList &items) const index = sameName; if (index < 0) { if (isActive) - unmatched << profile; + activeUnlisted = true; continue; } @@ -516,13 +520,13 @@ void NetworkModel::addWireless(const QString &device, QList &items) const } // Connected to a network no scan has listed, a hidden one for instance. - for (const QString &profile : std::as_const(unmatched)) { + if (activeUnlisted) { Network network; - network.ssid = WifiUtils::ssidKey(m_client->setting(profile, wirelessSetting, QStringLiteral("ssid")).toByteArray()); - network.profile = profile; - network.timestamp = lastUsed(m_client, profile); + network.ssid = WifiUtils::ssidKey(m_client->setting(activeProfile, wirelessSetting, QStringLiteral("ssid")).toByteArray()); + network.profile = activeProfile; + network.timestamp = lastUsed(m_client, activeProfile); network.accessPoint = activeAp; - switch (profileClass(m_client, profile)) { + switch (profileClass(m_client, activeProfile)) { case SecurityClass::Open: network.security = Enums::NoneSecurity; break; @@ -538,7 +542,7 @@ void NetworkModel::addWireless(const QString &device, QList &items) const } if (!activeAp.isEmpty()) network.strength = ap(activeAp, "Strength").toInt(); - addItem(network, device + QLatin1Char('|') + profile); + addItem(network, device + QLatin1Char('|') + activeProfile); } } @@ -587,13 +591,19 @@ void NetworkModel::addVpns(QList &items) const void NetworkModel::fillDetails(Item &item) const { - // Addresses are those of the device the network is up on. + // Addresses are those of the device the network is up on; a VPN's come from its + // active connection, as its row has no device. if (item.connectionState != Activated) return; const QString device = item.devicePath; - const QString ip4 = NetworkClient::path(m_client->property(device, deviceInterface, QStringLiteral("Ip4Config"))); - const QString ip6 = NetworkClient::path(m_client->property(device, deviceInterface, QStringLiteral("Ip6Config"))); + const QString vpn = item.type == Enums::Vpn ? VpnUtils::activeConnection(m_client, item.connectionPath) : QString(); + const auto config = [&](const QString &name) { + return NetworkClient::path(vpn.isEmpty() ? m_client->property(device, deviceInterface, name) + : m_client->property(vpn, activeInterface, name)); + }; + const QString ip4 = config(QStringLiteral("Ip4Config")); + const QString ip6 = config(QStringLiteral("Ip6Config")); item.ipV4Address = firstAddress(m_client->property(ip4, ip4ConfigInterface, QStringLiteral("AddressData")).toList(), false); item.ipV6Address = firstAddress(m_client->property(ip6, ip6ConfigInterface, QStringLiteral("AddressData")).toList(), true); diff --git a/network/secretagent.cpp b/network/secretagent.cpp index a0254d3..a0a8dd7 100644 --- a/network/secretagent.cpp +++ b/network/secretagent.cpp @@ -608,13 +608,24 @@ void NetworkSecretAgent::finish() Q_EMIT requestChanged(); } -void NetworkSecretAgent::dismiss(const std::function &matches) +void NetworkSecretAgent::dismissStalePrompts() { + if (!m_client->ready()) + return; + + // A prompt for a network that went out of range, a device that went away, or a device + // that started connecting elsewhere is out of date. bool headChanged = false; for (int i = m_requests.size() - 1; i >= 0; --i) { const Request &request = m_requests.at(i); - if (request.accessPointPath.isEmpty() || !matches(request)) + if (request.accessPointPath.isEmpty()) continue; + if (m_client->has(request.accessPointPath, accessPointInterface) + && m_client->has(request.devicePath, deviceInterface)) { + const QString active = m_client->activeConnection(request.devicePath); + if (active.isEmpty() || active == request.deviceActive) + continue; + } m_requests.removeAt(i); headChanged |= i == 0; } @@ -623,20 +634,4 @@ void NetworkSecretAgent::dismiss(const std::function &mat Q_EMIT requestChanged(); } -void NetworkSecretAgent::dismissStalePrompts() -{ - if (!m_client->ready()) - return; - - // A prompt for a network that went out of range, a device that went away, or a device - // that started connecting elsewhere is out of date. - dismiss([this](const Request &request) { - if (!m_client->has(request.accessPointPath, accessPointInterface) - || !m_client->has(request.devicePath, deviceInterface)) - return true; - const QString active = m_client->activeConnection(request.devicePath); - return !active.isEmpty() && active != request.deviceActive; - }); -} - #include "secretagent.moc" diff --git a/network/secretagent.h b/network/secretagent.h index e9b6cd2..08ef59f 100644 --- a/network/secretagent.h +++ b/network/secretagent.h @@ -7,7 +7,6 @@ #include #include #include -#include #include @@ -109,7 +108,6 @@ private: void fail(const Request &request, const QString &error, const QString &text); void ask(const Request &request); void finish(); - void dismiss(const std::function &matches); void dismissStalePrompts(); // Owned by the application, which may tear it down first. diff --git a/network/secretstore.cpp b/network/secretstore.cpp index e25312c..fca56e9 100644 --- a/network/secretstore.cpp +++ b/network/secretstore.cpp @@ -158,16 +158,16 @@ void SecretStore::save(const QString &uuid, const QString &connectionName, const QString &settingName, const QString &key, const QString &value, std::function done) { - const auto finish = [done](const QString &session, SecretStore *store) { + const auto finish = [this, done](const QString &session) { if (!session.isEmpty()) - store->closeSession(session); + closeSession(session); if (done) done(); }; openSession([=, this](const QString &session) { if (session.isEmpty()) { - finish(session, this); + finish(session); return; } @@ -176,13 +176,13 @@ void SecretStore::save(const QString &uuid, const QString &connectionName, const auto collection = reply.arguments().value(0).value(); if (reply.type() != QDBusMessage::ReplyMessage || collection.path() == noPrompt) { qWarning() << "Secret Service has no default collection to store" << key << "in"; - finish(session, this); + finish(session); return; } unlock({collection}, [=, this](const Paths &unlocked) { if (unlocked.isEmpty()) { - finish(session, this); + finish(session); return; } @@ -211,10 +211,10 @@ void SecretStore::save(const QString &uuid, const QString &connectionName, if (reply.type() == QDBusMessage::ReplyMessage && promptPath.path() != noPrompt) prompt(promptPath, [=, this](bool, const QVariant &) { - finish(session, this); + finish(session); }); else - finish(session, this); + finish(session); }); }); }); diff --git a/network/tests/fakenetworkmanager.h b/network/tests/fakenetworkmanager.h index de19e45..9f41640 100644 --- a/network/tests/fakenetworkmanager.h +++ b/network/tests/fakenetworkmanager.h @@ -5,7 +5,6 @@ #include #include #include -#include #include #include #include @@ -50,7 +49,6 @@ public: : m_bus(bus) { registerNetworkDBusTypes(); - qDBusRegisterMetaType>(); objects[FakeNM::managerPath][FakeNM::manager] = { {QStringLiteral("Version"), QStringLiteral("1.52.1")}, {QStringLiteral("State"), 70u}, diff --git a/network/tests/tst_networkclient.cpp b/network/tests/tst_networkclient.cpp index 4bc5f9a..2a16266 100644 --- a/network/tests/tst_networkclient.cpp +++ b/network/tests/tst_networkclient.cpp @@ -150,11 +150,7 @@ private slots: handler.addAndActivateConnection(wifiDevice, office, QStringLiteral("wrong pass")); QTRY_COMPARE(nm->count(QStringLiteral("AddAndActivateConnection")), 1); QTRY_COMPARE(NetworkClient::instance()->activeConnections().size(), 2); - QString active; - for (const QString &path : NetworkClient::instance()->activeConnections()) { - if (path != wiredActive) - active = path; - } + const QString active = otherActive(); QVERIFY(!active.isEmpty()); // NM_ACTIVE_CONNECTION_STATE_DEACTIVATED for NO_SECRETS. @@ -229,11 +225,7 @@ private slots: QTRY_VERIFY(handler.hotspotActive()); // The next start updates that profile rather than adding another. - QString active; - for (const QString &path : NetworkClient::instance()->activeConnections()) { - if (path != wiredActive) - active = path; - } + const QString active = otherActive(); nm->remove(active); QTRY_VERIFY(!handler.hotspotActive()); configuration.setHotspotName(QStringLiteral("Desk 2")); @@ -254,11 +246,7 @@ private slots: QSignalSpy failed(&handler, &Handler::hotspotFailed); handler.createHotspot(); QTRY_COMPARE(NetworkClient::instance()->activeConnections().size(), 2); - QString active; - for (const QString &path : NetworkClient::instance()->activeConnections()) { - if (path != wiredActive) - active = path; - } + const QString active = otherActive(); // NM_ACTIVE_CONNECTION_STATE_DEACTIVATED for DEVICE_DISCONNECTED. QTest::qWait(100); @@ -484,6 +472,16 @@ private: return {}; } + static QString otherActive() + { + QString active; + for (const QString &path : NetworkClient::instance()->activeConnections()) { + if (path != wiredActive) + active = path; + } + return active; + } + std::unique_ptr server; std::unique_ptr nm; }; diff --git a/network/tests/tst_vpnutils.cpp b/network/tests/tst_vpnutils.cpp index bf06094..83e035f 100644 --- a/network/tests/tst_vpnutils.cpp +++ b/network/tests/tst_vpnutils.cpp @@ -127,6 +127,17 @@ private slots: QCOMPARE(peers.first().value(QStringLiteral("allowed-ips")).toStringList(), (QStringList {QStringLiteral("0.0.0.0/0"), QStringLiteral("::/0")})); + const NMVariantMapMap ipv6Server = VpnUtils::manualProfile({{QStringLiteral("kind"), QStringLiteral("wireguard")}, + {QStringLiteral("server"), QStringLiteral("2001:db8::1")}, + {QStringLiteral("privateKey"), QStringLiteral("yAnz5TF+lXXJte14tji3zlMNq+hd2rYUIgJBgB3fBmk=")}, + {QStringLiteral("publicKey"), QStringLiteral("xTIBA5rboUvnH4htodjb6e697QjLERt1NAB4mZqp8Dg=")}, + {QStringLiteral("address"), QStringLiteral("10.0.0.2/24")}}, + &error); + QVERIFY2(!ipv6Server.isEmpty(), qPrintable(error)); + QCOMPARE(ipv6Server.value(QStringLiteral("wireguard")).value(QStringLiteral("peers")).value>() + .first().value(QStringLiteral("endpoint")).toString(), + QStringLiteral("[2001:db8::1]:51820")); + QVERIFY(VpnUtils::manualProfile({{QStringLiteral("kind"), QStringLiteral("pptp")}, {QStringLiteral("server"), QStringLiteral("x")}}, &error) diff --git a/network/tests/tst_wifiutils.cpp b/network/tests/tst_wifiutils.cpp index 98f2473..3a48486 100644 --- a/network/tests/tst_wifiutils.cpp +++ b/network/tests/tst_wifiutils.cpp @@ -63,10 +63,6 @@ private slots: QVERIFY(!WifiUtils::pskValid(QStringLiteral("1234567"))); QVERIFY(WifiUtils::pskValid(QString(64, QLatin1Char('a')))); QVERIFY(!WifiUtils::pskValid(QString(64, QLatin1Char('g')))); - QVERIFY(WifiUtils::wepKeyValid(QStringLiteral("abcde"))); - QVERIFY(WifiUtils::wepKeyValid(QStringLiteral("0123456789"))); - QVERIFY(!WifiUtils::wepKeyValid(QStringLiteral("012345678g"))); - QVERIFY(!WifiUtils::wepKeyValid(QStringLiteral("abcdef"))); QCOMPARE(WifiUtils::generatedPassword().size(), 12); QVERIFY(WifiUtils::pskValid(WifiUtils::generatedPassword())); } diff --git a/network/vpnutils.cpp b/network/vpnutils.cpp index 590ed5a..8ac6023 100644 --- a/network/vpnutils.cpp +++ b/network/vpnutils.cpp @@ -1,7 +1,6 @@ #include "vpnutils.h" #include "networkclient.h" -#include #include #include #include @@ -377,16 +376,16 @@ NMVariantMapMap wireGuardProfile(const QByteArray &content, const QString &name, uint fwmark = 0; uint mtu = 0; bool peerRoutes = true; - QVariantList addresses[2]; - QVariantList dns[2]; + QList addresses[2]; + QList dns4; + QList dns6; QStringList search; QList peers; // Like `wg setconf`: whitespace anywhere is dropped and '#' starts a comment. const QStringList lines = QString::fromUtf8(content).split(QLatin1Char('\n')); for (int number = 1; number <= lines.size(); ++number) { - QString line = lines.at(number - 1); - line = line.left(line.indexOf(QLatin1Char('#')) < 0 ? line.size() : line.indexOf(QLatin1Char('#'))); + QString line = lines.at(number - 1).section(QLatin1Char('#'), 0, 0); line.remove(QRegularExpression(QStringLiteral("\\s"))); if (line.isEmpty()) continue; @@ -434,9 +433,9 @@ NMVariantMapMap wireGuardProfile(const QByteArray &content, const QString &name, if (address.isNull()) search << word; else if (address.protocol() == QAbstractSocket::IPv4Protocol) - dns[0] << qToBigEndian(address.toIPv4Address()); + dns4 << qToBigEndian(address.toIPv4Address()); else - dns[1] << QByteArray(reinterpret_cast(address.toIPv6Address().c), 16); + dns6 << QByteArray(reinterpret_cast(address.toIPv6Address().c), 16); } } else if (section == Interface && field.compare(QLatin1String("MTU"), Qt::CaseInsensitive) == 0) { mtu = value.toUInt(&ok); @@ -515,24 +514,12 @@ NMVariantMapMap wireGuardProfile(const QByteArray &content, const QString &name, QVariantMap ip; ip.insert(QStringLiteral("method"), addresses[family].isEmpty() ? QStringLiteral("disabled") : QStringLiteral("manual")); if (!addresses[family].isEmpty()) { - QList data; - for (const QVariant &address : std::as_const(addresses[family])) - data << address.toMap(); - ip.insert(QStringLiteral("address-data"), QVariant::fromValue(data)); + ip.insert(QStringLiteral("address-data"), QVariant::fromValue(addresses[family])); // DNS needs an address of its family; without search domains, the server // answers for every name, as wg-quick has it. - if (!dns[family].isEmpty()) { - if (family == 0) { - QList servers; - for (const QVariant &server : std::as_const(dns[0])) - servers << server.toUInt(); - ip.insert(QStringLiteral("dns"), QVariant::fromValue(servers)); - } else { - QList servers; - for (const QVariant &server : std::as_const(dns[1])) - servers << server.toByteArray(); - ip.insert(QStringLiteral("dns"), QVariant::fromValue(servers)); - } + const bool hasDns = family == 0 ? !dns4.isEmpty() : !dns6.isEmpty(); + if (hasDns) { + ip.insert(QStringLiteral("dns"), family == 0 ? QVariant::fromValue(dns4) : QVariant::fromValue(dns6)); ip.insert(QStringLiteral("dns-search"), search.isEmpty() ? QStringList {QStringLiteral("~")} : search); } } @@ -577,6 +564,10 @@ NMVariantMapMap manualProfile(const QVariantMap &form, QString *error) if (kind == QLatin1String("wireguard")) { // Written out as a wg-quick file, so the form is checked the way imports are. QString endpoint = server; + // NetworkManager takes a bare IPv6 address's last group for the port, so it needs brackets. + if (QHostAddress(server).protocol() == QAbstractSocket::IPv6Protocol + && QHostAddress(server.section(QLatin1Char(':'), 0, -2)).isNull()) + endpoint = QStringLiteral("[%1]").arg(server); if (!endpoint.contains(QLatin1Char(':')) || endpoint.endsWith(QLatin1Char(']'))) endpoint += QStringLiteral(":51820"); QString config = QStringLiteral("[Interface]\nPrivateKey = %1\nAddress = %2\n") diff --git a/network/wifiutils.cpp b/network/wifiutils.cpp index 0c9beef..f6e6d60 100644 --- a/network/wifiutils.cpp +++ b/network/wifiutils.cpp @@ -322,24 +322,6 @@ bool pskValid(const QString &psk) return bytes.size() >= 8 && bytes.size() <= 63; } -bool wepKeyValid(const QString &key) -{ - if (key.size() == 10 || key.size() == 26) { - for (const QChar c : key) { - if (c.unicode() > 0x7f || !std::isxdigit(c.unicode())) - return false; - } - return true; - } - if (key.size() != 5 && key.size() != 13) - return false; - for (const QChar c : key) { - if (c.unicode() < 0x20 || c.unicode() > 0x7e) - return false; - } - return true; -} - QString generatedPassword() { // nmcli's alphabet, without the characters that read like others. diff --git a/network/wifiutils.h b/network/wifiutils.h index e6abc5e..eed5c14 100644 --- a/network/wifiutils.h +++ b/network/wifiutils.h @@ -31,7 +31,6 @@ CUTEFISH_NETWORK_EXPORT int strengthStep(int strength); CUTEFISH_NETWORK_EXPORT QString strengthIcon(int strength); CUTEFISH_NETWORK_EXPORT bool pskValid(const QString &psk); -CUTEFISH_NETWORK_EXPORT bool wepKeyValid(const QString &key); CUTEFISH_NETWORK_EXPORT QString generatedPassword(); } // namespace WifiUtils diff --git a/power/tests/batterystatus_test.cpp b/power/tests/batterystatus_test.cpp index 3fe9577..0351b69 100644 --- a/power/tests/batterystatus_test.cpp +++ b/power/tests/batterystatus_test.cpp @@ -4,7 +4,6 @@ #include "batterystatus.h" #include -#include #include #include diff --git a/screen/CMakeLists.txt b/screen/CMakeLists.txt index 3ec487a..46c8552 100644 --- a/screen/CMakeLists.txt +++ b/screen/CMakeLists.txt @@ -75,7 +75,6 @@ if(BUILD_TESTING) find_package(Qt6 REQUIRED COMPONENTS Test) add_executable(display-outputmodel-test tests/outputmodel_test.cpp) target_link_libraries(display-outputmodel-test PRIVATE cutefish-framework-screen Qt6::Test) - target_include_directories(display-outputmodel-test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) add_test(NAME display-outputmodel-test COMMAND display-outputmodel-test) add_executable(display-client-test tests/displayclient_test.cpp) target_link_libraries(display-client-test PRIVATE Cutefish::Display Qt6::Test) @@ -89,7 +88,6 @@ if(BUILD_TESTING) target_compile_definitions(display-screen-integration-test PRIVATE SCREEN_PROBE_PATH="$") target_link_libraries(display-screen-integration-test PRIVATE cutefish-framework-screen Qt6::Test) - target_include_directories(display-screen-integration-test PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}) add_test(NAME display-screen-integration-test COMMAND dbus-run-session -- $) set_tests_properties(display-screen-integration-test PROPERTIES diff --git a/screen/displayclient.cpp b/screen/displayclient.cpp index f8a9f34..a3c19c9 100644 --- a/screen/displayclient.cpp +++ b/screen/displayclient.cpp @@ -65,7 +65,7 @@ void DisplayClient::connectInterface() m_state = {}; m_loaded = false; emit stateChanged(); - if (m_applyPending || !m_operationToken.isEmpty() || m_completion) + if (operationPending()) finishOperation(m_operationToken, {false, false, 1, tr("The display service restarted."), {}}); delete m_interface; @@ -124,7 +124,7 @@ void DisplayClient::refresh() m_state = {}; emit stateChanged(); emit availabilityChanged(); - if (m_applyPending || !m_operationToken.isEmpty() || m_completion) + if (operationPending()) finishOperation(m_operationToken, {false, false, 1, reply.error().message(), {}}); return; } @@ -143,7 +143,7 @@ QString DisplayClient::apply(const QList &displays, const Q const QList &placements, const ipc::MirrorGroups &mirrorGroups) { - if (!available() || m_applyPending || !m_operationToken.isEmpty() || m_completion) + if (!available() || operationPending()) return {}; m_applyPending = true; m_operationToken.clear(); @@ -166,10 +166,8 @@ QString DisplayClient::apply(const QList &displays, const Q } else { m_operationToken = reply.value(); const auto result = m_earlyResults.constFind(m_operationToken); - if (result != m_earlyResults.cend()) { - const auto completed = *result; - queueCompletion(m_operationToken, completed); - } + if (result != m_earlyResults.cend()) + queueCompletion(m_operationToken, *result); m_earlyResults.clear(); } }); @@ -188,6 +186,11 @@ void DisplayClient::finishOperation(const QString &token, const OperationResult result.message, result.affected); } +bool DisplayClient::operationPending() const +{ + return m_applyPending || !m_operationToken.isEmpty() || m_completion; +} + void DisplayClient::queueCompletion(const QString &token, const OperationResult &result) { m_operationToken = token; diff --git a/screen/displayclient.h b/screen/displayclient.h index 857dd0a..b835bc6 100644 --- a/screen/displayclient.h +++ b/screen/displayclient.h @@ -85,6 +85,9 @@ private: bool sendRequest(const QDBusPendingCall &call, Completion done); void failRequests(); + // An apply waits for its reply, its OperationFinished, or the state that shows it. + bool operationPending() const; + struct OperationResult { bool succeeded; bool degraded; diff --git a/screen/outputmodel.cpp b/screen/outputmodel.cpp index b7eab88..d081dc3 100644 --- a/screen/outputmodel.cpp +++ b/screen/outputmodel.cpp @@ -97,8 +97,6 @@ QVariant DisplayOutputModel::data(const QModelIndex &index, int role) const return {}; const Entry &e = m_entries.at(index.row()); const auto *mode = selectedMode(e); - const auto resolutions = resolutionModes(e); - const auto refreshes = refreshModes(e); switch (role) { case Qt::DisplayRole: return name(e); @@ -122,15 +120,18 @@ QVariant DisplayOutputModel::data(const QModelIndex &index, int role) const return e.rotation == 0 ? 1 : e.rotation == 1 ? 2 : e.rotation == 2 ? 4 : 8; case ScaleRole: return e.scale120 / 120.0; - case ResolutionIndexRole: + case ResolutionIndexRole: { + const auto resolutions = resolutionModes(e); for (int i = 0; i < resolutions.size(); ++i) { const auto &m = e.physical.modes.at(resolutions.at(i)); if (mode && m.width == mode->width && m.height == mode->height) return i; } return 0; - case RecommendedResolutionIndexRole: + } + case RecommendedResolutionIndexRole: { // The display's preferred mode, the one the compositor starts it in. + const auto resolutions = resolutionModes(e); for (int i = 0; i < resolutions.size(); ++i) { const auto &m = e.physical.modes.at(resolutions.at(i)); for (const auto &preferred : e.physical.modes) { @@ -140,9 +141,11 @@ QVariant DisplayOutputModel::data(const QModelIndex &index, int role) const } } return -1; + } case RecommendedScaleRole: return e.physical.recommendedScale120 / 120.0; case ResolutionsRole: { + const auto resolutions = resolutionModes(e); QStringList v; for (int i : resolutions) { const auto &m = e.physical.modes.at(i); @@ -151,8 +154,9 @@ QVariant DisplayOutputModel::data(const QModelIndex &index, int role) const return v; } case RefreshRateIndexRole: - return refreshes.indexOf(e.modeIndex); + return refreshModes(e).indexOf(e.modeIndex); case RefreshRatesRole: { + const auto refreshes = refreshModes(e); QStringList v; for (int i : refreshes) v.append(QStringLiteral("%1 Hz").arg(e.physical.modes.at(i).refreshMilliHz / 1000.0, 0, @@ -266,12 +270,13 @@ bool DisplayOutputModel::setData(const QModelIndex &index, const QVariant &value if (selected < 0 || selected >= choices.size()) return false; const auto &t = e.physical.modes.at(choices.at(selected)); + const auto *current = selectedMode(e); + const int currentRefresh = current ? current->refreshMilliHz : 0; int best = choices.at(selected), difference = INT_MAX; for (int i = 0; i < e.physical.modes.size(); ++i) { const auto &c = e.physical.modes.at(i); if (c.width == t.width && c.height == t.height) { - const int d = qAbs(c.refreshMilliHz - - (selectedMode(e) ? selectedMode(e)->refreshMilliHz : 0)); + const int d = qAbs(c.refreshMilliHz - currentRefresh); if (d < difference) { best = i; difference = d; @@ -313,6 +318,18 @@ bool DisplayOutputModel::setData(const QModelIndex &index, const QVariant &value } m_layoutDirty |= role == PositionRole || role == NormalizedPositionRole || role == EnabledRole || role == ReplicationSourceIndexRole; + if (role == ReplicationSourceIndexRole && e.primary && !e.mirrorSource.isEmpty()) { + // The compositor takes the primary only from a mirror source. + for (auto &candidate : m_entries) { + if (candidate.physical.sessionId == e.mirrorSource && candidate.enabled + && candidate.mirrorSource.isEmpty()) { + e.primary = false; + candidate.primary = true; + emit dataChanged(this->index(0), this->index(m_entries.size() - 1), {PrimaryRole}); + break; + } + } + } if (role == EnabledRole && !e.enabled && e.primary) { e.primary = false; for (auto &candidate : m_entries) { @@ -377,12 +394,12 @@ void DisplayOutputModel::setState(const DisplayClient::State &state) } } for (const auto &physical : state.physical) { + int row = 0; + while (row < m_entries.size() && m_entries[row].physical.sessionId != physical.sessionId) + ++row; Entry entry; - for (const auto &previous : m_entries) - if (previous.physical.sessionId == physical.sessionId) { - entry = previous; - break; - } + if (row < m_entries.size()) + entry = m_entries[row]; entry.physical = physical; entry.enabled = false; entry.primary = false; @@ -405,9 +422,6 @@ void DisplayOutputModel::setState(const DisplayClient::State &state) entry.mirrorSource = logical.destinations.first(); break; } - int row = 0; - while (row < m_entries.size() && m_entries[row].physical.sessionId != physical.sessionId) - ++row; if (row == m_entries.size()) { beginInsertRows({}, row, row); m_entries.append(entry); diff --git a/screen/screen.cpp b/screen/screen.cpp index dfdf63b..0842aac 100644 --- a/screen/screen.cpp +++ b/screen/screen.cpp @@ -22,7 +22,7 @@ DisplayScreen::DisplayScreen(QObject *parent) [this](const QString &, bool succeeded, bool degraded, uint, const QString &message, const QStringList &) { m_busy = false; - m_error = succeeded ? (degraded ? message : QString()) : message; + m_error = succeeded && !degraded ? QString() : message; m_outputModel->setState(m_client.state()); emit stateChanged(); }); diff --git a/screen/tests/displayclient_test.cpp b/screen/tests/displayclient_test.cpp index 7d27f9d..b66e74c 100644 --- a/screen/tests/displayclient_test.cpp +++ b/screen/tests/displayclient_test.cpp @@ -1,9 +1,7 @@ -#include #include #include #include -#include #include #include #include diff --git a/screen/tests/outputmodel_test.cpp b/screen/tests/outputmodel_test.cpp index dc28b84..553a211 100644 --- a/screen/tests/outputmodel_test.cpp +++ b/screen/tests/outputmodel_test.cpp @@ -3,6 +3,31 @@ #include "outputmodel.h" +// Displays side by side from x = 0, each its own logical display, display 0 primary. +static DisplayClient::State extendedDisplays(int count) +{ + DisplayClient::State state; + for (int i = 0; i < count; ++i) { + cutefish::display::ipc::PhysicalDisplay physical; + physical.sessionId = QString::number(i); + cutefish::display::ipc::Mode mode; + mode.width = 1920; + mode.height = 1080; + mode.refreshMilliHz = 60000; + mode.current = true; + physical.modes.append(mode); + state.physical.append(physical); + cutefish::display::ipc::LogicalDisplay logical; + logical.id = physical.sessionId; + logical.destinations = {physical.sessionId}; + logical.primary = i == 0; + logical.scale120 = 120; + logical.x = i * 1920; + state.logical.append(logical); + } + return state; +} + class OutputModelTest : public QObject { Q_OBJECT @@ -47,6 +72,50 @@ private slots: QCOMPARE(model.data(selected, DisplayOutputModel::IdRole).toString(), QString("1")); } + void mirroringThePrimaryHandsItToTheSource() + { + DisplayOutputModel model; + model.setState(extendedDisplays(3)); + QSignalSpy changed(&model, &QAbstractItemModel::dataChanged); + // DisplayScreen applies on dirtyChanged, so the handoff has to come before it. + QString applied; + connect(&model, &DisplayOutputModel::dirtyChanged, [&] { + applied = model.primary(); + }); + QVERIFY(model.setData(model.index(0), 1, DisplayOutputModel::ReplicationSourceIndexRole)); + QCOMPARE(applied, QString("1")); + QCOMPARE(model.primary(), QString("1")); + QVERIFY(!model.data(model.index(0), DisplayOutputModel::PrimaryRole).toBool()); + QVERIFY(model.data(model.index(1), DisplayOutputModel::PrimaryRole).toBool()); + bool primaryChanged = false; + for (const auto &arguments : changed) { + const auto roles = arguments.at(2).value>(); + primaryChanged |= roles.contains(DisplayOutputModel::PrimaryRole); + } + QVERIFY(primaryChanged); + const auto placements = model.placements(); + QCOMPARE(placements.size(), 1); + QCOMPARE(placements[0].displayId, QString("2")); + QCOMPARE(placements[0].parentId, QString("1")); + } + + void mirroringThePrimaryNeedsAnExtendedSource() + { + // Display 1 is off in the first state and mirrors display 2 in the second. + auto off = extendedDisplays(3); + off.logical.removeAt(1); + auto mirroring = off; + mirroring.logical[1].destinations.append("1"); + for (const auto &state : {off, mirroring}) { + DisplayOutputModel model; + model.setState(state); + QVERIFY(model.setData(model.index(0), 1, + DisplayOutputModel::ReplicationSourceIndexRole)); + QCOMPARE(model.primary(), QString("0")); + QVERIFY(model.data(model.index(0), DisplayOutputModel::PrimaryRole).toBool()); + } + } + void selectedResolutionAndRefreshStayConsistent() { DisplayClient::State state; diff --git a/screen/tests/screen_integration_test.cpp b/screen/tests/screen_integration_test.cpp index 4a947d7..1dcb740 100644 --- a/screen/tests/screen_integration_test.cpp +++ b/screen/tests/screen_integration_test.cpp @@ -1,4 +1,3 @@ -#include #include #include #include