on xenial help sign in
Add watchers for xdg desktop file changes

This adds watchers for xdg desktop file changes. It also figures out if the appid id is "ubuntu-app-launch" or "standard xdg" style. This spawns the first code to replace most (if not all) of ubuntu-app-launch, but with QT! This fixes: https://github.com/ubports/ubuntu-touch/issues/1058

commit0a526739ee669cf2b944331595edd5da77e8b1a7 authorMarius Gripsgard <marius@ubports.com> dateWed 14 Aug 2019, 22:51 (7 years ago) parent2af5f2c
12 files changed+272−22
plugins/Unity/Launcher/CMakeLists.txt+1 plugins/Unity/Launcher/appdrawermodel.cpp+6−4 plugins/Unity/Launcher/appdrawermodel.h+2 plugins/Unity/Launcher/ualwrapper.cpp−11 plugins/Unity/Launcher/ualwrapper.h−5 plugins/Unity/Launcher/xdgwatcher.cpp+129 plugins/Unity/Launcher/xdgwatcher.h+46 tests/plugins/Unity/Launcher/CMakeLists.txt+1 tests/plugins/Unity/Launcher/appdrawermodeltest.cpp+3 tests/plugins/Unity/Launcher/ualwrapper.cpp−2 tests/plugins/Unity/Launcher/xdgwatcher.cpp+45 tests/plugins/Unity/Launcher/xdgwatcher.h+39
plugins/Unity/Launcher/CMakeLists.txt+1
@@ -26,6 +26,7 @@ set(QMLLAUNCHERPLUGIN_SRC
2626 asadapter.cpp
2727 appdrawermodel.cpp
2828 ualwrapper.cpp
29+ xdgwatcher.cpp
2930 ${CMAKE_SOURCE_DIR}/plugins/AccountsService/AccountsServiceDBusAdaptor.cpp
3031 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationManagerInterface.h
3132 ${APPLICATION_API_INCLUDEDIR}/unity/shell/application/ApplicationInfoInterface.h
plugins/Unity/Launcher/appdrawermodel.cpp+6−4
@@ -16,13 +16,15 @@
1616
1717#include "appdrawermodel.h"
1818#include "ualwrapper.h"
19+#include "xdgwatcher.h"
1920
2021#include <QDebug>
2122#include <QDateTime>
2223
2324AppDrawerModel::AppDrawerModel(QObject *parent):
2425 AppDrawerModelInterface(parent),
25− m_ual(new UalWrapper(this))
26+ m_ual(new UalWrapper(this)),
27+ m_xdgWatcher(new XdgWatcher(this))
2628{
2729 Q_FOREACH (const QString &appId, UalWrapper::installedApps()) {
2830 UalWrapper::AppInfo info = UalWrapper::getApplicationInfo(appId);
@@ -37,9 +39,9 @@ AppDrawerModel::AppDrawerModel(QObject *parent):
3739 }
3840
3941 // keep this a queued connection as it's coming from another thread.
40− connect(m_ual, &UalWrapper::appAdded, this, &AppDrawerModel::appAdded, Qt::QueuedConnection);
41− connect(m_ual, &UalWrapper::appRemoved, this, &AppDrawerModel::appRemoved, Qt::QueuedConnection);
42− connect(m_ual, &UalWrapper::appInfoChanged, this, &AppDrawerModel::appInfoChanged, Qt::QueuedConnection);
42+ connect(m_xdgWatcher, &XdgWatcher::appAdded, this, &AppDrawerModel::appAdded, Qt::QueuedConnection);
43+ connect(m_xdgWatcher, &XdgWatcher::appRemoved, this, &AppDrawerModel::appRemoved, Qt::QueuedConnection);
44+ connect(m_xdgWatcher, &XdgWatcher::appInfoChanged, this, &AppDrawerModel::appInfoChanged, Qt::QueuedConnection);
4345}
4446
4547int AppDrawerModel::rowCount(const QModelIndex &parent) const
plugins/Unity/Launcher/appdrawermodel.h+2
@@ -20,6 +20,7 @@
2020#include "launcheritem.h"
2121
2222class UalWrapper;
23+class XdgWatcher;
2324
2425class AppDrawerModel: public AppDrawerModelInterface
2526{
@@ -38,4 +39,5 @@ private Q_SLOTS:
3839private:
3940 QList<LauncherItem*> m_list;
4041 UalWrapper *m_ual;
42+ XdgWatcher *m_xdgWatcher;
4143};
plugins/Unity/Launcher/ualwrapper.cpp−11
@@ -24,17 +24,6 @@ using namespace ubuntu::app_launch;
2424UalWrapper::UalWrapper(QObject *parent):
2525 QObject(parent)
2626{
27−/* Disable for now
28− Registry::appAdded().connect([this](const std::shared_ptr<Application>&app) {
29− Q_EMIT appAdded(QString::fromStdString(app->appId()));
30− });
31− Registry::appRemoved().connect([this](const AppID &appId) {
32− Q_EMIT appRemoved(QString::fromStdString(appId));
33− });
34− Registry::appInfoUpdated().connect([this](const std::shared_ptr<Application>&app){
35− Q_EMIT appInfoChanged(QString::fromStdString(app->appId()));
36− });
37−*/
3827}
3928
4029QStringList UalWrapper::installedApps()
plugins/Unity/Launcher/ualwrapper.h−5
@@ -33,9 +33,4 @@ public:
3333
3434 static QStringList installedApps();
3535 static AppInfo getApplicationInfo(const QString &appId);
36−
37−Q_SIGNALS:
38− void appAdded(const QString &appId);
39− void appRemoved(const QString &appId);
40− void appInfoChanged(const QString &appId);
4136};
plugins/Unity/Launcher/xdgwatcher.cppnew file+129
@@ -0,0 +1,129 @@
1+/*
2+ * Copyright (C) 2019 UBports Foundation
3+ * Author(s): Marius Gripsgard <marius@ubports.com>
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+#include "xdgwatcher.h"
19+
20+#include <QDebug>
21+#include <QDir>
22+#include <QFile>
23+#include <QStandardPaths>
24+#include <QTextStream>
25+
26+XdgWatcher::XdgWatcher(QObject* parent)
27+ : QObject(parent),
28+ m_watcher(new QFileSystemWatcher(this))
29+{
30+ connect(m_watcher, &QFileSystemWatcher::directoryChanged, this, &XdgWatcher::onDirectoryChanged);
31+ connect(m_watcher, &QFileSystemWatcher::fileChanged, this, &XdgWatcher::onFileChanged);
32+
33+ const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
34+ for (const auto &path: paths) {
35+ const auto qdir = QDir(path);
36+ if (!qdir.exists()) {
37+ continue;
38+ }
39+
40+ // Add the path itself to watch for newly added apps
41+ m_watcher->addPath(path);
42+
43+ // Add watcher for eatch app to watch for changes
44+ const auto files = qdir.entryInfoList(QDir::Files);
45+ for (const auto &file: files) {
46+ if (file.suffix() == "desktop") {
47+ const auto path = file.absoluteFilePath();
48+ m_watcher->addPath(path);
49+ m_registry.insert(path, getAppId(file));
50+ }
51+ }
52+ }
53+}
54+
55+// "Ubuntu style" appID is filename without versionNumber after last "_"
56+const QString XdgWatcher::stripAppIdVersion(const QString rawAppID) const {
57+ auto appIdComponents = rawAppID.split("_");
58+ appIdComponents.removeLast();
59+ return appIdComponents.join("_");
60+}
61+
62+// Standard appID see:
63+// https://standards.freedesktop.org/desktop-entry-spec/desktop-entry-spec-latest.html#desktop-file-id
64+const QString XdgWatcher::toStandardAppId(const QFileInfo fileInfo) const {
65+ const auto paths = QStandardPaths::standardLocations(QStandardPaths::ApplicationsLocation);
66+ for (const auto &path: paths) {
67+ if (fileInfo.absolutePath() == path) {
68+ break;
69+ }
70+ if (fileInfo.absolutePath().contains(path)) {
71+ auto fileStr = fileInfo.absoluteFilePath();
72+ fileStr.replace(path, "");
73+ fileStr.replace("/", "-");
74+ fileStr.replace(".desktop", "");
75+ return fileStr;
76+ }
77+ }
78+ return fileInfo.completeBaseName();
79+}
80+
81+const QString XdgWatcher::getAppId(const QFileInfo fileInfo) const {
82+ // We need to open the file to check if its and Ual application
83+ // as we cant just rely on the app name as "normal" apps can also
84+ // contain 3 _ causing us to belive its an ubuntu app
85+ // Example kde_org_kate would become kde_org
86+ QFile qFile(fileInfo.absoluteFilePath());
87+ qFile.open(QIODevice::ReadOnly);
88+ QTextStream fileStream(&qFile);
89+ QString line;
90+ while (fileStream.readLineInto(&line)) {
91+ if (line.startsWith("X-Ubuntu-Application-ID=")) {
92+ auto rawAppID = line.replace("X-Ubuntu-Application-ID=", "");
93+ qFile.close();
94+ return stripAppIdVersion(rawAppID);
95+ }
96+ }
97+ qFile.close();
98+
99+ // If it's not an "Ubuntu" appID, we follow freedesktop standard
100+ return toStandardAppId(fileInfo);
101+}
102+
103+// Watch for newly added apps
104+void XdgWatcher::onDirectoryChanged(const QString &path) {
105+ const auto files = QDir(path).entryInfoList(QDir::Files);
106+ const auto watchedFiles = m_watcher->files();
107+ for (const auto &file: files) {
108+ const auto appPath = file.absoluteFilePath();
109+ if (file.suffix() == "desktop" && !watchedFiles.contains(appPath)) {
110+ m_watcher->addPath(appPath);
111+
112+ const auto appId = getAppId(file);
113+ m_registry.insert(appPath, appId);
114+ Q_EMIT appAdded(appId);
115+ }
116+ }
117+}
118+
119+void XdgWatcher::onFileChanged(const QString &path) {
120+ QFileInfo file(path);
121+ if (file.exists()) {
122+ // The file exists, this must be an modify event
123+ Q_EMIT appInfoChanged(m_registry.value(path));
124+ } else {
125+ // File does not exist, assume this is an remove event.
126+ // onDirectoryChanged will handle rename event
127+ Q_EMIT appRemoved(m_registry.take(path));
128+ }
129+}
plugins/Unity/Launcher/xdgwatcher.hnew file+46
@@ -0,0 +1,46 @@
1+/*
2+ * Copyright (C) 2019 UBports Foundation
3+ * Author(s): Marius Gripsgard <marius@ubports.com>
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+#include <QFileSystemWatcher>
19+#include <QHash>
20+#include <QFileInfo>
21+
22+class QFileInfo;
23+
24+class XdgWatcher: public QObject
25+{
26+ Q_OBJECT
27+public:
28+ XdgWatcher(QObject* parent = nullptr);
29+
30+Q_SIGNALS:
31+ void appAdded(const QString &appId);
32+ void appRemoved(const QString &appId);
33+ void appInfoChanged(const QString &appId);
34+
35+private Q_SLOTS:
36+ void onDirectoryChanged(const QString &path);
37+ void onFileChanged(const QString &path);
38+
39+private:
40+ const QString toStandardAppId(const QFileInfo fileInfo) const;
41+ const QString getAppId(const QFileInfo file) const;
42+ const QString stripAppIdVersion(const QString rawAppID) const;
43+
44+ QFileSystemWatcher* m_watcher;
45+ QHash<const QString, QString> m_registry;
46+};
tests/plugins/Unity/Launcher/CMakeLists.txt+1
@@ -65,6 +65,7 @@ add_unity8_unittest(LauncherModel dbus-test-runner
6565add_executable(appdrawermodeltestExec
6666 appdrawermodeltest.cpp
6767 ualwrapper.cpp
68+ xdgwatcher.cpp
6869 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/appdrawermodel.cpp
6970 ${LAUNCHER_API_INCLUDEDIR}/unity/shell/launcher/AppDrawerModelInterface.h
7071 ${CMAKE_SOURCE_DIR}/plugins/Unity/Launcher/launcheritem.cpp
tests/plugins/Unity/Launcher/appdrawermodeltest.cpp+3
@@ -15,6 +15,7 @@
1515 */
1616
1717#include "ualwrapper.h"
18+#include "xdgwatcher.h"
1819#include "appdrawermodel.h"
1920
2021#include <QtTest>
@@ -38,11 +39,13 @@ private Q_SLOTS:
3839 QCOMPARE(appDrawerModel->rowCount(QModelIndex()), 2);
3940
4041 UalWrapper::instance()->addMockApp("app3");
42+ XdgWatcher::instance()->addMockApp("app3");
4143 qApp->processEvents(); // ualwrapper is connected Queued
4244
4345 QCOMPARE(appDrawerModel->rowCount(QModelIndex()), 3);
4446
4547 UalWrapper::instance()->removeMockApp("app3");
48+ XdgWatcher::instance()->removeMockApp("app3");
4649 qApp->processEvents();
4750
4851 QCOMPARE(appDrawerModel->rowCount(QModelIndex()), 2);
tests/plugins/Unity/Launcher/ualwrapper.cpp−2
@@ -50,11 +50,9 @@ UalWrapper::AppInfo UalWrapper::getApplicationInfo(const QString &appId)
5050void UalWrapper::addMockApp(const QString &appId)
5151{
5252 s_list.append(appId);
53− Q_EMIT appAdded(appId);
5453}
5554
5655void UalWrapper::removeMockApp(const QString &appId)
5756{
5857 s_list.removeAll(appId);
59− Q_EMIT appRemoved(appId);
6058}
tests/plugins/Unity/Launcher/xdgwatcher.cppnew file+45
@@ -0,0 +1,45 @@
1+/*
2+ * Copyright (C) 2019 UBports Foundation.
3+ * Author(s): Marius Gripsgard <marius@ubports.com>
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+#include "xdgwatcher.h"
19+
20+XdgWatcher* XdgWatcher::s_xinstance = nullptr;
21+
22+XdgWatcher::XdgWatcher(QObject* parent)
23+ : QObject(parent)
24+{
25+ s_xinstance = this;
26+}
27+
28+XdgWatcher* XdgWatcher::instance()
29+{
30+ return s_xinstance;
31+}
32+
33+void XdgWatcher::addMockApp(const QString &appId)
34+{
35+ Q_EMIT appAdded(appId);
36+}
37+
38+void XdgWatcher::removeMockApp(const QString &appId)
39+{
40+ Q_EMIT appRemoved(appId);
41+}
42+
43+const QString XdgWatcher::stripAppIdVersion(const QString rawAppID) {
44+ return rawAppID;
45+}
tests/plugins/Unity/Launcher/xdgwatcher.hnew file+39
@@ -0,0 +1,39 @@
1+/*
2+ * Copyright (C) 2019 UBports Foundation.
3+ * Author(s): Marius Gripsgard <marius@ubports.com>
4+ *
5+ * This program is free software; you can redistribute it and/or modify
6+ * it under the terms of the GNU General Public License as published by
7+ * the Free Software Foundation; version 3.
8+ *
9+ * This program is distributed in the hope that it will be useful,
10+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
11+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12+ * GNU General Public License for more details.
13+ *
14+ * You should have received a copy of the GNU General Public License
15+ * along with this program. If not, see <http://www.gnu.org/licenses/>.
16+ */
17+
18+#include <QObject>
19+
20+class XdgWatcher: public QObject
21+{
22+ Q_OBJECT
23+public:
24+ XdgWatcher(QObject* parent = nullptr);
25+
26+ // testing
27+ static XdgWatcher* instance();
28+ static const QString stripAppIdVersion(const QString rawAppID);
29+ void addMockApp(const QString &appId);
30+ void removeMockApp(const QString &appId);
31+Q_SIGNALS:
32+ void appAdded(const QString &appId);
33+ void appRemoved(const QString &appId);
34+ void appInfoChanged(const QString &appId);
35+
36+private:
37+ static QStringList s_list;
38+ static XdgWatcher *s_xinstance;
39+};