mirror of
https://invent.kde.org/marcoa/shift-shell.git
synced 2026-06-11 08:57:21 +00:00
Record app launches from window creation so recent and most-used lists survive after windows close. Use those models in the convergence launcher and keep the hover surface in the unified chrome.
208 lines
No EOL
6 KiB
C++
208 lines
No EOL
6 KiB
C++
// SPDX-FileCopyrightText: 2026 Marco Allegretti
|
|
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
#include "applicationusagemodel.h"
|
|
|
|
#include "applicationlistmodel.h"
|
|
#include "foliodelegate.h"
|
|
#include "foliosettings.h"
|
|
#include "homescreen.h"
|
|
|
|
#include <QJsonArray>
|
|
#include <QJsonDocument>
|
|
#include <QJsonObject>
|
|
|
|
namespace
|
|
{
|
|
constexpr qsizetype s_maxStoredEntries = 24;
|
|
|
|
QString normalizedStorageId(const QString &storageId)
|
|
{
|
|
if (storageId.isEmpty()) {
|
|
return {};
|
|
}
|
|
|
|
if (storageId.endsWith(QLatin1String(".desktop"))) {
|
|
return storageId;
|
|
}
|
|
|
|
return storageId + QStringLiteral(".desktop");
|
|
}
|
|
}
|
|
|
|
ApplicationUsageStore::ApplicationUsageStore(HomeScreen *parent)
|
|
: QObject{parent}
|
|
, m_homeScreen{parent}
|
|
{
|
|
load();
|
|
}
|
|
|
|
QList<ApplicationUsageEntry> ApplicationUsageStore::entries() const
|
|
{
|
|
return m_entries.values();
|
|
}
|
|
|
|
void ApplicationUsageStore::recordUsage(const QString &storageId)
|
|
{
|
|
const QString normalizedId = normalizedStorageId(storageId);
|
|
if (normalizedId.isEmpty()) {
|
|
return;
|
|
}
|
|
|
|
auto &entry = m_entries[normalizedId];
|
|
entry.storageId = normalizedId;
|
|
entry.launchCount = std::max(0, entry.launchCount) + 1;
|
|
entry.lastUsed = QDateTime::currentDateTimeUtc();
|
|
|
|
save();
|
|
Q_EMIT usageChanged();
|
|
}
|
|
|
|
void ApplicationUsageStore::load()
|
|
{
|
|
m_entries.clear();
|
|
|
|
if (!m_homeScreen) {
|
|
return;
|
|
}
|
|
|
|
const QJsonDocument doc = QJsonDocument::fromJson(m_homeScreen->folioSettings()->applicationUsage().toUtf8());
|
|
const QJsonArray usageArray = doc.array();
|
|
|
|
for (const QJsonValue &value : usageArray) {
|
|
const QJsonObject object = value.toObject();
|
|
const QString storageId = normalizedStorageId(object.value(QStringLiteral("storageId")).toString());
|
|
if (storageId.isEmpty()) {
|
|
continue;
|
|
}
|
|
|
|
ApplicationUsageEntry entry;
|
|
entry.storageId = storageId;
|
|
entry.launchCount = object.value(QStringLiteral("launchCount")).toInt();
|
|
entry.lastUsed = QDateTime::fromString(object.value(QStringLiteral("lastUsed")).toString(), Qt::ISODateWithMs);
|
|
|
|
if (entry.launchCount <= 0 || !entry.lastUsed.isValid()) {
|
|
continue;
|
|
}
|
|
|
|
m_entries.insert(storageId, entry);
|
|
}
|
|
}
|
|
|
|
void ApplicationUsageStore::save()
|
|
{
|
|
if (!m_homeScreen) {
|
|
return;
|
|
}
|
|
|
|
QList<ApplicationUsageEntry> entries = m_entries.values();
|
|
std::sort(entries.begin(), entries.end(), [](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
|
|
return left.lastUsed > right.lastUsed;
|
|
});
|
|
|
|
if (entries.size() > s_maxStoredEntries) {
|
|
entries.resize(s_maxStoredEntries);
|
|
}
|
|
|
|
QJsonArray usageArray;
|
|
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
|
|
QJsonObject object;
|
|
object.insert(QStringLiteral("storageId"), entry.storageId);
|
|
object.insert(QStringLiteral("launchCount"), entry.launchCount);
|
|
object.insert(QStringLiteral("lastUsed"), entry.lastUsed.toString(Qt::ISODateWithMs));
|
|
usageArray.append(object);
|
|
}
|
|
|
|
m_homeScreen->folioSettings()->setApplicationUsage(QString::fromUtf8(QJsonDocument(usageArray).toJson(QJsonDocument::Compact)));
|
|
}
|
|
|
|
ApplicationUsageModel::ApplicationUsageModel(HomeScreen *homeScreen, ApplicationUsageStore *store, Mode mode)
|
|
: QAbstractListModel{homeScreen}
|
|
, m_homeScreen{homeScreen}
|
|
, m_store{store}
|
|
, m_mode{mode}
|
|
{
|
|
if (m_store) {
|
|
connect(m_store, &ApplicationUsageStore::usageChanged, this, &ApplicationUsageModel::rebuild);
|
|
}
|
|
|
|
if (m_homeScreen && m_homeScreen->applicationListModel()) {
|
|
auto *applicationListModel = m_homeScreen->applicationListModel();
|
|
connect(applicationListModel, &QAbstractItemModel::rowsInserted, this, &ApplicationUsageModel::rebuild);
|
|
connect(applicationListModel, &QAbstractItemModel::rowsRemoved, this, &ApplicationUsageModel::rebuild);
|
|
connect(applicationListModel, &QAbstractItemModel::modelReset, this, &ApplicationUsageModel::rebuild);
|
|
}
|
|
|
|
rebuild();
|
|
}
|
|
|
|
int ApplicationUsageModel::rowCount(const QModelIndex &parent) const
|
|
{
|
|
if (parent.isValid()) {
|
|
return 0;
|
|
}
|
|
|
|
return m_entries.size();
|
|
}
|
|
|
|
QVariant ApplicationUsageModel::data(const QModelIndex &index, int role) const
|
|
{
|
|
if (!index.isValid() || index.row() < 0 || index.row() >= m_entries.size()) {
|
|
return QVariant();
|
|
}
|
|
|
|
const ApplicationUsageEntry &entry = m_entries.at(index.row());
|
|
switch (role) {
|
|
case Qt::DisplayRole:
|
|
case DelegateRole:
|
|
return QVariant::fromValue(m_delegates.at(index.row()).get());
|
|
case LaunchCountRole:
|
|
return entry.launchCount;
|
|
case LastUsedRole:
|
|
return entry.lastUsed;
|
|
default:
|
|
return QVariant();
|
|
}
|
|
}
|
|
|
|
QHash<int, QByteArray> ApplicationUsageModel::roleNames() const
|
|
{
|
|
return {
|
|
{DelegateRole, QByteArrayLiteral("delegate")},
|
|
{LaunchCountRole, QByteArrayLiteral("launchCount")},
|
|
{LastUsedRole, QByteArrayLiteral("lastUsed")},
|
|
};
|
|
}
|
|
|
|
void ApplicationUsageModel::rebuild()
|
|
{
|
|
QList<ApplicationUsageEntry> entries = m_store ? m_store->entries() : QList<ApplicationUsageEntry>{};
|
|
|
|
std::sort(entries.begin(), entries.end(), [this](const ApplicationUsageEntry &left, const ApplicationUsageEntry &right) {
|
|
if (m_mode == MostUsed && left.launchCount != right.launchCount) {
|
|
return left.launchCount > right.launchCount;
|
|
}
|
|
|
|
return left.lastUsed > right.lastUsed;
|
|
});
|
|
|
|
QList<ApplicationUsageEntry> nextEntries;
|
|
QList<std::shared_ptr<FolioDelegate>> nextDelegates;
|
|
|
|
if (m_homeScreen && m_homeScreen->applicationListModel()) {
|
|
for (const ApplicationUsageEntry &entry : std::as_const(entries)) {
|
|
auto delegate = m_homeScreen->applicationListModel()->delegateForStorageId(entry.storageId);
|
|
if (!delegate || !delegate->application()) {
|
|
continue;
|
|
}
|
|
|
|
nextEntries.append(entry);
|
|
nextDelegates.append(delegate);
|
|
}
|
|
}
|
|
|
|
beginResetModel();
|
|
m_entries = nextEntries;
|
|
m_delegates = nextDelegates;
|
|
endResetModel();
|
|
} |