summaryrefslogtreecommitdiff
path: root/plugin/src/Caelestia
diff options
context:
space:
mode:
author2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-09-11 02:03:01 +1000
committer2 * r + 2 * t <61896496+soramanew@users.noreply.github.com>2025-09-11 02:03:01 +1000
commit2832de18d71579383b400c1bf9ecbcb0ef54ce10 (patch)
tree62237ba1a81b243e5a1f2e3ae86d9fb9d802b4bf /plugin/src/Caelestia
parentplugin: remove multimedia dep (diff)
downloadcaelestia-shell-2832de18d71579383b400c1bf9ecbcb0ef54ce10.tar.gz
caelestia-shell-2832de18d71579383b400c1bf9ecbcb0ef54ce10.tar.bz2
caelestia-shell-2832de18d71579383b400c1bf9ecbcb0ef54ce10.zip
launcher: sort apps by usage
Closes #588
Diffstat (limited to 'plugin/src/Caelestia')
-rw-r--r--plugin/src/Caelestia/CMakeLists.txt4
-rw-r--r--plugin/src/Caelestia/appdb.cpp201
-rw-r--r--plugin/src/Caelestia/appdb.hpp93
3 files changed, 297 insertions, 1 deletions
diff --git a/plugin/src/Caelestia/CMakeLists.txt b/plugin/src/Caelestia/CMakeLists.txt
index 1a96bf4..c13417f 100644
--- a/plugin/src/Caelestia/CMakeLists.txt
+++ b/plugin/src/Caelestia/CMakeLists.txt
@@ -1,4 +1,4 @@
-find_package(Qt6 REQUIRED COMPONENTS Core Qml Gui Concurrent)
+find_package(Qt6 REQUIRED COMPONENTS Core Qml Gui Concurrent Sql)
find_package(PkgConfig REQUIRED)
pkg_check_modules(Qalculate IMPORTED_TARGET libqalculate REQUIRED)
pkg_check_modules(Pipewire IMPORTED_TARGET libpipewire-0.3 REQUIRED)
@@ -21,6 +21,7 @@ qt_add_qml_module(caelestia
audiocollector.hpp audiocollector.cpp
audioprovider.hpp audioprovider.cpp
cavaprovider.hpp cavaprovider.cpp
+ appdb.hpp appdb.cpp
)
qt_query_qml_module(caelestia
@@ -45,6 +46,7 @@ target_link_libraries(caelestia PRIVATE
Qt::Qml
Qt::Gui
Qt::Concurrent
+ Qt::Sql
PkgConfig::Qalculate
PkgConfig::Pipewire
PkgConfig::Aubio
diff --git a/plugin/src/Caelestia/appdb.cpp b/plugin/src/Caelestia/appdb.cpp
new file mode 100644
index 0000000..20e37bb
--- /dev/null
+++ b/plugin/src/Caelestia/appdb.cpp
@@ -0,0 +1,201 @@
+#include "appdb.hpp"
+
+#include <qsqldatabase.h>
+#include <qsqlquery.h>
+#include <quuid.h>
+
+namespace caelestia {
+
+AppEntry::AppEntry(QObject* entry, unsigned int frequency, QObject* parent)
+ : QObject(parent)
+ , m_entry(entry)
+ , m_frequency(frequency) {}
+
+QObject* AppEntry::entry() const {
+ return m_entry;
+}
+
+quint32 AppEntry::frequency() const {
+ return m_frequency;
+}
+
+void AppEntry::setFrequency(unsigned int frequency) {
+ if (m_frequency != frequency) {
+ m_frequency = frequency;
+ emit frequencyChanged();
+ }
+}
+
+void AppEntry::incrementFrequency() {
+ m_frequency++;
+ emit frequencyChanged();
+}
+
+QString AppEntry::id() const {
+ return m_entry->property("id").toString();
+}
+
+QString AppEntry::name() const {
+ return m_entry->property("name").toString();
+}
+
+QString AppEntry::desc() const {
+ return m_entry->property("comment").toString();
+}
+
+QString AppEntry::execString() const {
+ return m_entry->property("execString").toString();
+}
+
+QString AppEntry::wmClass() const {
+ return m_entry->property("startupClass").toString();
+}
+
+QString AppEntry::genericName() const {
+ return m_entry->property("genericName").toString();
+}
+
+QString AppEntry::categories() const {
+ return m_entry->property("categories").toStringList().join(" ");
+}
+
+QString AppEntry::keywords() const {
+ return m_entry->property("keywords").toStringList().join(" ");
+}
+
+AppDb::AppDb(QObject* parent)
+ : QObject(parent)
+ , m_uuid(QUuid::createUuid().toString()) {
+ auto db = QSqlDatabase::addDatabase("QSQLITE", m_uuid);
+ db.setDatabaseName(":memory:");
+ db.open();
+
+ QSqlQuery query(db);
+ query.exec("CREATE TABLE IF NOT EXISTS frequencies (id TEXT PRIMARY KEY, frequency INTEGER)");
+}
+
+QString AppDb::uuid() const {
+ return m_uuid;
+}
+
+QString AppDb::path() const {
+ return m_path;
+}
+
+void AppDb::setPath(const QString& path) {
+ if (m_path == path) {
+ return;
+ }
+
+ m_path = path;
+ emit pathChanged();
+
+ auto db = QSqlDatabase::database(m_uuid, false);
+ db.close();
+ db.setDatabaseName(path);
+
+ updateAppFrequencies();
+}
+
+QList<QObject*> AppDb::entries() const {
+ return m_entries;
+}
+
+void AppDb::setEntries(const QList<QObject*>& entries) {
+ if (m_entries == entries) {
+ return;
+ }
+
+ m_entries = entries;
+ emit entriesChanged();
+
+ updateApps();
+}
+
+QList<AppEntry*> AppDb::apps() const {
+ auto apps = m_apps.values();
+ std::sort(apps.begin(), apps.end(), [](AppEntry* a, AppEntry* b) {
+ if (a->frequency() != b->frequency()) {
+ return a->frequency() > b->frequency();
+ }
+ return a->name().localeAwareCompare(b->name()) < 0;
+ });
+ return apps;
+}
+
+void AppDb::incrementFrequency(const QString& id) {
+ auto db = QSqlDatabase::database(m_uuid);
+ QSqlQuery query(db);
+
+ query.prepare("INSERT INTO frequencies (id, frequency) "
+ "VALUES (:id, 1) "
+ "ON CONFLICT (id) DO UPDATE SET frequency = frequency + 1");
+ query.bindValue(":id", id);
+ query.exec();
+
+ for (auto app : m_apps) {
+ if (app->id() == id) {
+ const auto before = apps();
+
+ app->incrementFrequency();
+
+ if (before != apps()) {
+ emit appsChanged();
+ }
+
+ return;
+ }
+ }
+
+ qWarning() << "AppDb::incrementFrequency: could not find app with id" << id;
+}
+
+quint32 AppDb::getFrequency(const QString& id) const {
+ auto db = QSqlDatabase::database(m_uuid);
+ QSqlQuery query(db);
+
+ query.prepare("SELECT frequency FROM frequencies WHERE id = :id");
+ query.bindValue(":id", id);
+
+ if (query.exec() && query.next()) {
+ return query.value(0).toUInt();
+ }
+
+ return 0;
+}
+
+void AppDb::updateAppFrequencies() {
+ for (auto app : m_apps) {
+ app->setFrequency(getFrequency(app->id()));
+ }
+}
+
+void AppDb::updateApps() {
+ bool dirty = false;
+
+ for (auto entry : m_entries) {
+ const auto id = entry->property("id").toString();
+ if (!m_apps.contains(id)) {
+ dirty = true;
+ m_apps.insert(id, new AppEntry(entry, getFrequency(id), this));
+ }
+ }
+
+ QSet<QString> newIds;
+ for (auto entry : m_entries) {
+ newIds.insert(entry->property("id").toString());
+ }
+
+ for (auto id : m_apps.keys()) {
+ if (!newIds.contains(id)) {
+ dirty = true;
+ delete m_apps.take(id);
+ }
+ }
+
+ if (dirty) {
+ emit appsChanged();
+ }
+}
+
+} // namespace caelestia
diff --git a/plugin/src/Caelestia/appdb.hpp b/plugin/src/Caelestia/appdb.hpp
new file mode 100644
index 0000000..dfedcc6
--- /dev/null
+++ b/plugin/src/Caelestia/appdb.hpp
@@ -0,0 +1,93 @@
+#pragma once
+
+#include <qhash.h>
+#include <qobject.h>
+#include <qqmlintegration.h>
+
+namespace caelestia {
+
+class AppEntry : public QObject {
+ Q_OBJECT
+ QML_ELEMENT
+ QML_UNCREATABLE("AppEntry instances can only be retrieved from an AppDb")
+
+ // The actual DesktopEntry, but we don't have access to the type so it's a QObject
+ Q_PROPERTY(QObject* entry READ entry CONSTANT)
+
+ Q_PROPERTY(quint32 frequency READ frequency NOTIFY frequencyChanged)
+ Q_PROPERTY(QString id READ id CONSTANT)
+ Q_PROPERTY(QString name READ name CONSTANT)
+ Q_PROPERTY(QString desc READ desc CONSTANT)
+ Q_PROPERTY(QString execString READ execString CONSTANT)
+ Q_PROPERTY(QString wmClass READ wmClass CONSTANT)
+ Q_PROPERTY(QString genericName READ genericName CONSTANT)
+ Q_PROPERTY(QString categories READ categories CONSTANT)
+ Q_PROPERTY(QString keywords READ keywords CONSTANT)
+
+public:
+ explicit AppEntry(QObject* entry, quint32 frequency, QObject* parent = nullptr);
+
+ [[nodiscard]] QObject* entry() const;
+
+ [[nodiscard]] quint32 frequency() const;
+ void setFrequency(quint32 frequency);
+ void incrementFrequency();
+
+ [[nodiscard]] QString id() const;
+ [[nodiscard]] QString name() const;
+ [[nodiscard]] QString desc() const;
+ [[nodiscard]] QString execString() const;
+ [[nodiscard]] QString wmClass() const;
+ [[nodiscard]] QString genericName() const;
+ [[nodiscard]] QString categories() const;
+ [[nodiscard]] QString keywords() const;
+
+signals:
+ void frequencyChanged();
+
+private:
+ QObject* m_entry;
+ quint32 m_frequency;
+};
+
+class AppDb : public QObject {
+ Q_OBJECT
+ QML_ELEMENT
+
+ Q_PROPERTY(QString uuid READ uuid CONSTANT)
+ Q_PROPERTY(QString path READ path WRITE setPath NOTIFY pathChanged REQUIRED)
+ Q_PROPERTY(QList<QObject*> entries READ entries WRITE setEntries NOTIFY entriesChanged REQUIRED)
+ Q_PROPERTY(QList<AppEntry*> apps READ apps NOTIFY appsChanged)
+
+public:
+ explicit AppDb(QObject* parent = nullptr);
+
+ [[nodiscard]] QString uuid() const;
+
+ [[nodiscard]] QString path() const;
+ void setPath(const QString& path);
+
+ [[nodiscard]] QList<QObject*> entries() const;
+ void setEntries(const QList<QObject*>& entries);
+
+ [[nodiscard]] QList<AppEntry*> apps() const;
+
+ Q_INVOKABLE void incrementFrequency(const QString& id);
+
+signals:
+ void pathChanged();
+ void entriesChanged();
+ void appsChanged();
+
+private:
+ const QString m_uuid;
+ QString m_path;
+ QList<QObject*> m_entries;
+ QHash<QString, AppEntry*> m_apps;
+
+ quint32 getFrequency(const QString& id) const;
+ void updateAppFrequencies();
+ void updateApps();
+};
+
+} // namespace caelestia